Home > database >  React - How to toggle class of a single element in a .map() function
React - How to toggle class of a single element in a .map() function

Time:10-13

I'm new to react and I am trying to toggle a class for a specific element inside a loop.

const ItemList: React.FC<ListItemUserProps> = (props) => {

  const { items } = props;

  const [showUserOpt, setShowUserOpt] = useState<boolean>(false);

  function toggleUserOpt() {
    setShowUserOpt(!showUserOpt);
  }

  const userOptVisible = showUserOpt ? 'show' : 'hide';

  return (
    <>
      {items.map((t) => (
        <React.Fragment key={t.userId}>
          <div
            className={`item ${userOptVisible}`}
            role="button"
            tabIndex={0}
            onClick={() => toggleUserOpt()}
            onKeyDown={() => toggleUserOpt()}
          >
            {t.userNav.firstName}
          </div>
        </React.Fragment>
      ))}
    </>
  );
};

export default ItemList;

when I click on an element, the class toggles for every single one. I know this is really dumb but can someone help me?

Thanks!

CodePudding user response:

You can create another component that can have it's own state that can be toggled without effecting other sibling components' state:

Child:

const ItemListItem: React.FC<SomeInterface> = ({ item }) => {
  const [show, setShow] = useState<boolean>(false);

  const userOptVisible = show ? "show" : "hide";

  const toggleUserOpt = (e) => {
    setShow((prevState) => !prevState);
  };

  return (
    <div
      className={`item ${userOptVisible}`}
      role="button"
      tabIndex={0}
      onClick={toggleUserOpt}
      onKeyDown={toggleUserOpt}
    >
      {item.userNav.firstName}
    </div>
  );
};

Parent:

const ItemList: React.FC<ListItemUserProps> = ({ items }) => {
  return (
    <>
      {items.map((t) => (
        <ItemListItem key={t.userId} item={t} />
      ))}
    </>
  );
};

CodePudding user response:

If you simply adding classes to the element, I would keep it simple and use a handler to toggle the class using pure JS.

const handleClick = (e) => {
   // example of simply toggling a class
   e.currentTarget.classList.toggle('selected');
};

Demo:

const {
  useState,
} = React;

// dummy data
const data = Array(20).fill(null).map((i, index) => `item ${(index   1).toString()}`);

function App() {
  const [items, setItems] = useState(data);
  
  const handleClick = (e) => {
    e.currentTarget.classList.toggle('selected');
  };

  return ( 
    <div> 
      {items.map((item) => (
        <button key={item} onClick={handleClick}>{item}</button>
      ))} 
    </div>
  );
}

ReactDOM.render( <
  App / > ,
  document.getElementById("app")
);
.selected {
   background: red;
}
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>



<div id="app"></div>

CodePudding user response:

I think it'd be best if you kept track of the index so that you could target a single item in your list. As it stands the boolean is going to change the styling for all as you haven't specified which one should get the className.

Add a useState hook to keep track of it like:

const [activeIndex, setActiveIndex] = useState(null);

Then create a new function:

function handleIndexOnClick(index) {
    setActive(index);
  }

Then in your map() function add index. You'll then need to pass index in to you className attribute and the onClick function. The end result for that bit should look like:

{items.map((t, index) => (
        <React.Fragment key={t.userId}>
          <div
            className={`item ${activeIndex && items[activeIndex] ? 'show' : 'hide }`}
            role="button"
            tabIndex={0}
            onClick={() => handleIndexOnClick(index)}
            onKeyDown={() => toggleUserOpt()}
          >
            {t.userNav.firstName}
          </div>
        </React.Fragment>
      ))}
  • Related