0

I have a useState in nextjs that has values like this:

const [sampleData, setSampleData] = useState({
     value1: ''
     value2: ''
     value3: []
   });

If I want to update the state of value1 for instance while keeping the rest of the state, I would do something like this:

setSampleData({...sampleData, value1: 'new value'});

This works. However, if I want to update the state of value3 which is an array and still keep the previous array contents it has before, how do I achieve this? Doing something like this would replace the entire state of value3 array with the new set of arrays:

 setSampleData({...sampleData, value3: [new array]});

Is it possible or should I simply use another format entirely such as useReducer hook?

Jonas
  • 121,568
  • 97
  • 310
  • 388
kingNodejs
  • 39
  • 1
  • 7

3 Answers3

3

You can use an inner spread such as:

setSampleData({
  ...sampleData,
  value3: [...sampleData.value3, 'new value']
});
Paul-Marie
  • 874
  • 1
  • 6
  • 24
0

currecntly you have some options to achieve this,

Option1:

 setSampleData({...sampleData, value3: [...sampleData.value3, newValue]});

Option2:

const temp = {...sampleData}
temp.value3.push(newValue)
setSampleData({...temp});
Mahmoud Hassan
  • 328
  • 1
  • 8
  • Option2 can still cause rerendering issues since you're still modifying the original `value3` array from the original state. When you modify a nested value you need to also update all the levels of nesting leading down to that nested value like you have with your first example. – Nick Parsons Jun 07 '23 at 09:39
  • you are right, it needed to be copied not only referenced, good catch – Mahmoud Hassan Jun 07 '23 at 09:48
  • No worries, even with the edit you made though, you could still face issues. For example, if you had a component and were passing `value3` into it like so ``, and the `SomeComponent` was memoized, then `value3` in this case is still the same reference to what it was beforehand, so `SomeComponent` won't rerender even though you've changed it. – Nick Parsons Jun 07 '23 at 10:18
  • but isn't changing props force re-render the child component? small snippet: https://codesandbox.io/p/sandbox/elastic-tdd-h1o06r?file=%2Fsrc%2FApp.tsx%3A22%2C13 – Mahmoud Hassan Jun 08 '23 at 11:16
  • Yes, but not if the child componennt is memoized and you're passing the child an object, for example: https://codesandbox.io/p/sandbox/elastic-tdd-h1o06r?file=%2Fsrc%2FApp.tsx%3A22%2C13 – Nick Parsons Jun 08 '23 at 11:26
  • But that's not the only case, for example, a common react pattern is to use useEffect() to listen to prop changes (such as exaplained here: [How to sync props to state using React hooks : setState()](https://stackoverflow.com/a/54626764)), which while I don't really agree with this pattern, if used, it wouldn't work if the prop was `temp.value3` as the object reference is still the same. Other examples of how this could cause issues are also provided in the new React docs: https://react.dev/learn/updating-arrays-in-state#updating-objects-inside-arrays and – Nick Parsons Jun 08 '23 at 11:31
  • here: https://react.dev/learn/updating-objects-in-state#objects-are-not-really-nested. It's subtle, and most probably the above code for option2 will work for most cases, but there are cases where it will cause issues, and to avoid those head-scratching scenarios, it's best to update all levels of nesting the encapsulated the item being updated. – Nick Parsons Jun 08 '23 at 11:33
0

For adding extra logic to the setter, You can also pass a callback function to the state setter as such (instead of just the new state)

You can refer the docs to read more about it, but I guess this should work for your case.

setSampleData((prevSampleData) => {
  const newSampleData = {...prevSampleData};
  newSampleData?.value3.push("New Sample") // add your own logic here
  return newSampleData
});

The returned value of this callback is what gets set as the new state

nsrCodes
  • 625
  • 10
  • 25
  • This modifies the original state with `.push()` so this will cause issues with rerendering, especially if `value3` is being used within the returned JSX (FYI @kingNodejs) – Nick Parsons Jun 07 '23 at 09:30
  • 1
    For object values, it is safer to return a new object instead of mutating the old one: `const newSampleData = {...prevSampleData};` – pjaoko Jun 07 '23 at 10:01
  • @PJaoko thanks for pointing this out, updated the answer with this – nsrCodes Jun 07 '23 at 15:40
  • 1
    Your updated code can still cause issues on some scenarios. For example if the array at value3 is referenced in some other state, or if it’s passed to a memoized component. Coping just the top level object isn’t enough – Nick Parsons Jun 08 '23 at 08:40