Conditionally Styling React Components Based on Variable Value in State - DAV-1000/react-material GitHub Wiki

Created: 8/5/2025 13:38
Updated: 8/5/2025 14:14
Exported: 8/14/2025 11:46
Link: https://chatgpt.com/c/6891faad-d318-832a-bbb3-73da514c48cd

Prompt:

On my React page I have a number of Material UI Chip components. I want to toggle whether the sx is applied based on whether the value of page variable 'selectedTag' is the same value as a string which the component passes to funciton handleTagClick when it is clicked. What is the best approach for this. The current markup for a Chip on the page is here: <Chip onClick={() => handleTagClick('company')} size="medium" label="Company" sx={{ backgroundColor: 'transparent', border: 'none', }} />

Response:

Got it — you basically want each Chip to visually reflect whether it’s the currently selected tag, and to update that state when clicked.

The best way to handle this is:

  1. Keep selectedTag in React state.
  2. Pass the tag string into handleTagClick so it can update that state.
  3. In the sx prop, conditionally set styles based on whether the chip’s tag matches selectedTag.

Here’s a clean example:

import { useState } from 'react';
import Chip from '@mui/material/Chip';

export default function TagChips() {
  const [selectedTag, setSelectedTag] = useState(null);

  const handleTagClick = (tag) => {
    setSelectedTag(tag);
  };

  const getChipStyles = (tag) => ({
    backgroundColor: selectedTag === tag ? 'primary.main' : 'transparent',
    color: selectedTag === tag ? 'white' : 'text.primary',
    border: selectedTag === tag ? 'none' : '1px solid',
    borderColor: 'grey.400',
    cursor: 'pointer',
  });

  return (
    <>
      <Chip
        onClick={() => handleTagClick('company')}
        size="medium"
        label="Company"
        sx={getChipStyles('company')}
      />
      <Chip
        onClick={() => handleTagClick('team')}
        size="medium"
        label="Team"
        sx={getChipStyles('team')}
      />
      <Chip
        onClick={() => handleTagClick('project')}
        size="medium"
        label="Project"
        sx={getChipStyles('project')}
      />
    </>
  );
}

Why this works well:

  • Single source of truthselectedTag tracks which chip is active.
  • Reusable style logicgetChipStyles(tag) makes it easy to style any number of chips consistently.
  • Clear conditional → The sx prop directly compares the tag to selectedTag.

If you want clicking the same chip again to deselect it, you can change the handler to:

const handleTagClick = (tag) => {
  setSelectedTag((prev) => (prev === tag ? null : tag));
};

That will toggle it on/off.


Do you want me to rewrite your current chip markup so that all of them automatically use this toggle behavior without repeating sx code? That would make it very DRY.

Prompt:

Yes

Response:

Alright — let’s make it DRY so you’re not repeating sx everywhere and can easily add new chips just by adding a label/tag pair to a list.

Here’s a refactored version:

import { useState } from 'react';
import Chip from '@mui/material/Chip';

export default function TagChips() {
  const [selectedTag, setSelectedTag] = useState(null);

  const handleTagClick = (tag) => {
    setSelectedTag((prev) => (prev === tag ? null : tag)); // toggle on/off
  };

  const getChipStyles = (tag) => ({
    backgroundColor: selectedTag === tag ? 'primary.main' : 'transparent',
    color: selectedTag === tag ? 'white' : 'text.primary',
    border: selectedTag === tag ? 'none' : '1px solid',
    borderColor: selectedTag === tag ? 'primary.main' : 'grey.400',
    cursor: 'pointer',
  });

  const tags = [
    { tag: 'company', label: 'Company' },
    { tag: 'team', label: 'Team' },
    { tag: 'project', label: 'Project' },
    { tag: 'status', label: 'Status' },
  ];

  return (
    <>
      {tags.map(({ tag, label }) => (
        <Chip
          key={tag}
          onClick={() => handleTagClick(tag)}
          size="medium"
          label={label}
          sx={getChipStyles(tag)}
        />
      ))}
    </>
  );
}

How this helps

  • Only one style function (getChipStyles) → central place to adjust active/inactive styles.
  • Only one click handler → toggling works for all chips.
  • List-driven rendering → adding/removing chips is just editing tags array.
  • DRY → no repeated sx objects all over the JSX.

If you wanted to make the styling even easier to maintain, you could also define the active/inactive colors in your MUI theme so getChipStyles only needs to check selectedTag.


If you like, I can also make it so multiple chips can be active at once instead of just one. Would you like that version?

Prompt:

Can we refactor TagChips to be a separate component

Response:

Yes — that’s actually a good pattern here, because it keeps your page component cleaner and lets you reuse TagChips anywhere.

Here’s the refactor:

TagChips.jsx

import { useState } from 'react';
import Chip from '@mui/material/Chip';

export default function TagChips({ tags, onSelectionChange }) {
  const [selectedTag, setSelectedTag] = useState(null);

  const handleTagClick = (tag) => {
    setSelectedTag((prev) => {
      const newSelection = prev === tag ? null : tag;
      if (onSelectionChange) {
        onSelectionChange(newSelection);
      }
      return newSelection;
    });
  };

  const getChipStyles = (tag) => ({
    backgroundColor: selectedTag === tag ? 'primary.main' : 'transparent',
    color: selectedTag === tag ? 'white' : 'text.primary',
    border: selectedTag === tag ? 'none' : '1px solid',
    borderColor: selectedTag === tag ? 'primary.main' : 'grey.400',
    cursor: 'pointer',
  });

  return (
    <>
      {tags.map(({ tag, label }) => (
        <Chip
          key={tag}
          onClick={() => handleTagClick(tag)}
          size="medium"
          label={label}
          sx={getChipStyles(tag)}
        />
      ))}
    </>
  );
}

Usage in your page

import TagChips from './TagChips';

export default function MyPage() {
  const tags = [
    { tag: 'company', label: 'Company' },
    { tag: 'team', label: 'Team' },
    { tag: 'project', label: 'Project' },
    { tag: 'status', label: 'Status' },
  ];

  const handleTagSelection = (selectedTag) => {
    console.log('Selected Tag:', selectedTag);
    // You can update state or trigger filtering here
  };

  return (
    <div>
      <h1>My Page</h1>
      <TagChips tags={tags} onSelectionChange={handleTagSelection} />
    </div>
  );
}

Benefits of this approach:

  • The TagChips component is self-contained but still reports selection changes upward via onSelectionChange.
  • The styling logic is encapsulated and not scattered across your page.
  • You can drop <TagChips> into any other page with different tags data and it will just work.

Do you want me to make this new TagChips component support multiple selections as an option via a prop? That would make it more flexible for future use.

Prompt:

Use typescript

Response:

Gotcha — here’s the fully refactored TypeScript version of TagChips as a reusable component.


TagChips.tsx

import { useState } from 'react';
import Chip from '@mui/material/Chip';

type Tag = {
  tag: string;
  label: string;
};

interface TagChipsProps {
  tags: Tag[];
  onSelectionChange?: (selectedTag: string | null) => void;
}

export default function TagChips({ tags, onSelectionChange }: TagChipsProps) {
  const [selectedTag, setSelectedTag] = useState<string | null>(null);

  const handleTagClick = (tag: string) => {
    setSelectedTag((prev) => {
      const newSelection = prev === tag ? null : tag;
      if (onSelectionChange) {
        onSelectionChange(newSelection);
      }
      return newSelection;
    });
  };

  const getChipStyles = (tag: string) => ({
    backgroundColor: selectedTag === tag ? 'primary.main' : 'transparent',
    color: selectedTag === tag ? 'white' : 'text.primary',
    border: selectedTag === tag ? 'none' : '1px solid',
    borderColor: selectedTag === tag ? 'primary.main' : 'grey.400',
    cursor: 'pointer',
  });

  return (
    <>
      {tags.map(({ tag, label }) => (
        <Chip
          key={tag}
          onClick={() => handleTagClick(tag)}
          size="medium"
          label={label}
          sx={getChipStyles(tag)}
        />
      ))}
    </>
  );
}

Usage Example

import TagChips from './TagChips';

export default function MyPage() {
  const tags = [
    { tag: 'company', label: 'Company' },
    { tag: 'team', label: 'Team' },
    { tag: 'project', label: 'Project' },
    { tag: 'status', label: 'Status' },
  ];

  const handleTagSelection = (selectedTag: string | null) => {
    console.log('Selected Tag:', selectedTag);
    // Handle filtering or UI updates here
  };

  return (
    <div>
      <h1>My Page</h1>
      <TagChips tags={tags} onSelectionChange={handleTagSelection} />
    </div>
  );
}

Why this is solid

  • Type safety for props (Tag type and TagChipsProps).
  • Clean toggle logic.
  • Easy to reuse — you can drop <TagChips> anywhere with different tags.
  • Optional onSelectionChange so you only pass it if needed.

If you want, I can extend this TypeScript component so it supports multi-select mode with a simple prop like multiSelect={true} — would make it even more flexible.

Prompt:

This line: generates this warning: The expected type comes from property 'tags' which is declared here on type 'IntrinsicAttributes & TagChipsProps'

Response:

Ah — I see what’s going on.

That warning means TypeScript thinks the props you’re passing into <TagChips> don’t match the TagChipsProps type. The most likely reason is that tags in your page isn’t typed as an array of { tag: string; label: string }.

Right now, TagChipsProps expects:

tags: { tag: string; label: string }[];

So if your tags array is declared without an explicit type, TypeScript might be inferring the wrong shape (for example, string[]), which causes that error.


Fix

In your page component, explicitly type your tags array:

import TagChips from './TagChips';

type Tag = {
  tag: string;
  label: string;
};

export default function MyPage() {
  const tags: Tag[] = [
    { tag: 'company', label: 'Company' },
    { tag: 'team', label: 'Team' },
    { tag: 'project', label: 'Project' },
    { tag: 'status', label: 'Status' },
  ];

  const handleTagClick = (selectedTag: string | null) => {
    console.log('Selected Tag:', selectedTag);
  };

  return (
    <div>
      <h1>My Page</h1>
      <TagChips tags={tags} onSelectionChange={handleTagClick} />
    </div>
  );
}

Why this fixes it

By typing tags as Tag[], we guarantee it matches TagChipsProps.tags, so TypeScript stops complaining.

If you want to avoid having to re-declare the Tag type in multiple files, you can export it from TagChips.tsx:

export type Tag = {
  tag: string;
  label: string;
};

Then in your page:

import TagChips, { Tag } from './TagChips';

That way both the component and the consuming page share the same type definition.


Do you want me to make this change so TagChips exports its types and you never have to re-declare them? That’s a nice long-term maintenance win.


Powered by ChatGPT Exporter

⚠️ **GitHub.com Fallback** ⚠️