1

I have a state that is an array of objects

  const [objectsArray, setObjectsArray] = useState([
    { name: "first", active: false },
    { name: "second", active: false },
    { name: "third", active: false },
  ]);

I map this array and render a div for each object

<div>
  {objectsArray.map((obj,index) => (
    <div className={obj.active ? "active" : "inactive"} key={obj.name}>
      {obj.name}
    </div>
  ))}
</div>

As you can see above, the div class is conditionary rendered according to the property 'active'. I want to make an onClick function that toggle the 'active' value between true and false.

I was taught that the correct way of doing it should be this:

const toggler = (objectToBeToggled: number) => {
    setObjectsArray((prevState) =>
      prevState.map((obj, index) => {
        if (index === objectToBeToggled) {
          return { ...obj, active: !obj.active };
        }
        return obj;
      })
    );
  };


    <div>
      {objectsArray.map((obj, index) => (
        <div
          className={obj.active ? "active" : "inactive"}
          onClick={() => toggler(index)}
          key={obj.name}
        >
          {obj.name}
        </div>
      ))}
    </div>

this works completely fine, but as a begginer, the first 2 ideas that cames in my mind was doing the toggler function like this:

first idea that also works

  const toggler = (objectToBeToggled: number) => {
    let newState = [];
    newState = [...objectsArray];
    newState[objectToBeToggled].active = !newState[objectToBeToggled].active;
    setObjectsArray(newState);
  };

or like this:

second idea that doesn't work

  const toggler = (objectToBeToggled: number) => {
    setObjectsArray((prevState) => {
      let newState = [];
      newState = [...prevState];
      newState[objectToBeToggled].active = !newState[objectToBeToggled].active;
      return newState;
    });
  };

Could someone please explain me why does the second idea is wrong and doesn't work? Why can't I use the same logic as I used in the first idea inside the setter function? And talking about the first idea, is there any problem in directly spreading the state 'objectsArray' to create the let newState ?

ninern
  • 11
  • 2
  • both of them are correct and work just fine. see [this Q&A](https://stackoverflow.com/a/73637600/633183) to understand why a map might be better than an array here. – Mulan Sep 07 '22 at 19:25

1 Answers1

0

My guess is that second option does not work because it's changing a preexisting variable making it an impure function. [...array] is doing a shallow copy so your directly mutating state when you access object by index. In strict mode, react is calling your updater function twice to catch this kind of behavior, what is happening is probably that your boolean value gets toggled two times.

To fix it you should also shallow copy the object to update.

const toggler = (objectToBeToggled: number) => {
        setObjectsArray((p) => {
            let newState = [...p];
            const toUpdate = {...newState[objectToBeToggled]};
            toUpdate.active = !toUpdate.active;
            newState[objectToBeToggled] = toUpdate
            return newState;
        });
    };

Note that this means that the first approach, even if it works in this case, is also to avoid.

Reifocs
  • 704
  • 3
  • 16