1

I'm working in React. I'm using useState to hold an object in state. To make one change to the object and return it I can use the spread operator like so ...

 const [formWizard, setFormWizard] = useState(FORM_CONTROLS.steps[index]);

...

 const addSection = section => {
...

   setFormWizard(wizard => ({
     ...wizard,
     wizard: formWizard.controls[14].group.push(...subjectAddressCopy.slice(0, 5))
   }));
}

But what if I need to make multiple changes? I've tried putting a comma between them, use &&, and wrapping it in an object (though I think that would change the data structure). In either event known of those things worked.

   setFormWizard(wizard => ({
      ...wizard,
      wizard: formWizard.controls[14].group.push(...subjectAddressCopy.slice(0, 5),
      formWizard.controls[14].trash) // how can I also make this change?
   }));

To be clear, I want to add the property of trash to my object and set it to true. How can I make two changes to formWizard and return it?

London804
  • 1,072
  • 1
  • 22
  • 47
  • https://stackoverflow.com/a/43041334/1924257. Using `immer`'s produce would allow you to continue 'mutating' state in place. – wegry Apr 14 '22 at 20:55
  • 1
    `push()` returns the length of the array, I don't think that's what you want in your wizard property. – Cesare Polonara Apr 14 '22 at 20:57
  • push() is what I want and that's fine. My question is regarding the second line. How can I make that second change? – London804 Apr 14 '22 at 21:59
  • push is not what you want. You are just mutating your starting object and don't even know it. You also are not providing any information on what your 2nd change is. What else do you want to change? – Brandon Apr 14 '22 at 22:01
  • Yes, that's exactly what I want. With the second change I would like to add a property of 'trash' to my object and set it to true. – London804 Apr 14 '22 at 22:07

2 Answers2

1

It's bad practice to mutate the state, which is what you do when you push to .controls[14].group. The reason for this is that in some cases React won't notice the change and thus not trigger re-render.

Also you should not have a new state depend on the former state, unless that state comes from the functional setState update. The reason for this is that the former state might not be up to date if multiple updates are queued up. In your suggestion you mix wizard (which is the old state you are allowed to rely on), with formWizard (which is the old state you can't rely on). Only use wizard here.

To update .controls[14].group and .controls[14].trash without mutation you can do the following

setFormWizard(wizard => {
  const controls = wizard.controls[14]
  const groups = [...controls.group, ...subjectAddressCopy.slice(0, 5)]
  return ({
    ...wizard,
    controls: [
      ...wizard.controls.splice(0,14), // keep first 14 entries 
      {
        ...controls,
        group: groups,
        trash: true
      } // modify the 15th entry
      ...wizard.controls.splice(15) // keep the succeeding entries
    ],
    wizard: groups.length
  });
});

I agree with Brandon and believe you don't want the result of the push operation in the wizard property. But since you seem certain that's what you want, wizard: groups.length gives you the same.

I use the spread operator throughout to create new arrays and objects. Here's an article on alternative approaches. https://medium.com/@kkranthi438/dont-mutate-state-in-react-6b25d5e06f42

Anton
  • 1,045
  • 1
  • 7
  • 16
  • Hey Brandon thanks for posting this. In another function in my code I’m copying the first 5 items in an array mutating them and then adding them to the end of my array. I’m essentially creating the ability to duplicate and delete parts of a form. – London804 Apr 18 '22 at 20:03
0

The answer was kind of obvious. Posting this here in case anyone else comes across this question. In my setFormWizard function I was just returning an object so if I want to mutate the object more than once I can mutate it above the return statement and then just return it.

  setFormWizard(wizard => {
    wizard.controls[14].trash = true;
    return ({
      ...wizard,
      wizard: formWizard.controls[14].group.push(...subjectAddressCopy.slice(0, 5)),
    });
  });
London804
  • 1,072
  • 1
  • 22
  • 47
  • In this solution you mutate your state and you use the old state (which might not be up to date), both these are bad practices. I posted a solution that do the update in a safer way. Your solution will work in a lot of cases, but at some point it might create bugs which will be really hard to find. – Anton Apr 18 '22 at 08:34