3

I'm displaying different cars and a button to add or remove the selections the user has made. How do I get the buttons to change state individually? As of now, it changes the state of all the buttons to one value.

const cars = [
  { name: "Benz", selected: false },
  { name: "Jeep", selected: false },
  { name: "BMW", selected: false }
];

export default function App() {
  const isJeepSelected = true;
  const isBenzSelected = true;

  return (
    <div className="App">
      {cars.map((values, index) => (
        <div key={index}>
          <Item
            isBenzSelected={isBenzSelected}
            isJeepSelected={isJeepSelected}
            {...values}
          />
        </div>
      ))}
    </div>
  );
}

const Item = ({ name, isBenzSelected, isJeepSelected }) => {
  const [toggle, setToggle] = useState(false);
  const handleChange = () => {
    setToggle(!toggle);
  };

  if (isBenzSelected) {
    cars.find((val) => val.name === "Benz").selected = true;
  }

  console.log("cars --> ", cars);
  console.log("isBenzSelected ", isBenzSelected);
  console.log("isJeepSelected ", isJeepSelected);

  return (
    <>
      <span>{name}</span>
      <span>
        <button onClick={handleChange}>
          {!toggle && !isBenzSelected ? "Add" : "Remove"}
        </button>
      </span>
    </>
  );
};

I created a working example using Code Sandbox. Could anyone please help?

1 Answer 1

1

There's too much hardcoding here. What if you had 300 cars? You'd have to write 300 boolean useState hook calls, and it still wouldn't be dynamic if you had an arbitrary API payload (the usual case).

Try to think about how to generalize your logic rather than hardcoding values like "Benz" and Jeep. Those concepts are too closely-tied to the arbitrary data contents.

cars seems like it should be state since you're mutating it from React.

Here's an alternate approach:

const App = () => {
  const [cars, setCars] = React.useState([
    {name: "Benz", selected: false},
    {name: "Jeep", selected: false},
    {name: "BMW", selected: false},
  ]);
  
  const handleSelect = i => {
    setCars(prevCars => prevCars.map((e, j) =>
      ({...e, selected: i === j ? !e.selected : e.selected})
    ));
  };

  return (
    <div className="App">
      {cars.map((e, i) => (
        <div key={e.name}>
          <Item {...e} handleSelect={() => handleSelect(i)} />
        </div>
      ))}
    </div>
  );
};

const Item = ({name, selected, handleSelect}) => (
  <React.Fragment>
    <span>{name}</span>
    <span>
      <button onClick={handleSelect}>
        {selected ? "Remove" : "Add"}
      </button>
    </span>
  </React.Fragment>
);

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

Consider generating unique ids for elements rather than using indices or assuming the name is unique. crypto.randomUUID() is a handy way to do this.

Sign up to request clarification or add additional context in comments.

1 Comment

This is great! Lifting the state up in the root component and passing down the handleSelect function did the trick for me, perfect for my use case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.