Redux specifies that "The key to updating nested data is that every level of nesting must be copied and updated appropriately."
// changing reference in top level
const newState = {...oldState}
newState.x.y.z = 10;
setState(newState)
// or updating reference in all levels
const newState = {...oldState}
newState.x = {...newState.x}
newState.x.y = {..newState.x.y}
newState.x.y.z = 10
Is this is same for react, can't find the documentation regarding this on react.
CodePudding user response:
data types in JS can be divided into primitive data types (eg. string
, number
) and non-primitive data types (eg. object
),
primitive data types can not be modified (eg. using String.prototype.replace
will always return a new instance of a string) while non-primitive data types can - and the reference pointing to that data will be "updated" as well (that's a big simplification, but executing eg. x.y = 2
will not create a new instance of x
- instead the x
will be keept the same in every place it's referenced),
which means to detect a change in the new version of the state (in a most performant way) it is required to create a new instance of an Object
, Array
(and other non-primitive data type representations)
// changing reference in top level
const newState = { ...oldState }
// changing nested reference
const newState = {
...oldState,
x: {
...oldState.x,
y: 2 // new "y" value
}
}
// changing nested reference, another example
const newState = {
...oldState,
x: {
...oldState.x,
y: {
...oldState.x.y,
z: 'test' // new "z" value
}
}
}
you can read more how change is detected on a basic level here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#comparing_objects
CodePudding user response:
Yes, you should apply this concept when updating your state in React also. If you don't, you can have issues with components not re-rendering when you update your state. Normally, React will rerender your component when your state changes, in both of your examples you're doing this, as you're creating a new object reference at the top-level which will cause React to rerender your component and any of its child components (ie: the children components also get rerender when the parent component rerenders). However, there is an exception to this. If you've memoized a component to help with efficiency using React.memo()
, your memoized child component will only update when the props that it's passed change. If you are passing a child component an object that is nested within your original state, React won't rerender that component unless that object reference has changed. This could lead to issues if your memoized children components are trying to use state from your parent component.
For example:
const { useState, memo } = React;
const App = () => {
const [state, setState] = useState({
x: {
y: {
z: 0
}
}
});
const changeStateBad = () => {
// Your first technique (can cause issues)
const newState = {...state};
newState.x.y.z = 1;
setState(newState);
}
const changeStateGood = () => {
// Your second technique for updating at all levels (just rewritten slightly)
const newState = {
...state,
x: {
...state.x,
y: {
...state.x.y,
z: state.x.y.z 1
}
}
};
setState(newState);
}
return <div>
<NumberDisplay x={state.x} />
<br />
<button onClick={changeStateGood}>Good update</button>
<br />
<button onClick={changeStateBad}>Bad update</button>
</div>
}
// A memoized component that only rerenders when its props update
const NumberDisplay = memo(({x}) => {
return <h1>{x.y.z}</h1>;
});
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>