I\'m trying to organize my state by using nested property like this:
this.state = {
someProperty: {
flag:true
}
}
But updating
This is clearly not the right or best way to do, however it is cleaner to my view:
this.state.hugeNestedObject = hugeNestedObject;
this.state.anotherHugeNestedObject = anotherHugeNestedObject;
this.setState({})
However, React itself should iterate thought nested objects and update state and DOM accordingly which is not there yet.
I take very seriously the concerns already voiced around creating a complete copy of your component state. With that said, I would strongly suggest Immer.
import produce from 'immer';
<Input
value={this.state.form.username}
onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
This should work for React.PureComponent
(i.e. shallow state comparisons by React) as Immer
cleverly uses a proxy object to efficiently copy an arbitrarily deep state tree. Immer is also more typesafe compared to libraries like Immutability Helper, and is ideal for Javascript and Typescript users alike.
Typescript utility function
function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s:
Draft<Readonly<S>>) => any) {
comp.setState(produce(comp.state, s => { fn(s); }))
}
onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}
In order to setState
for a nested object you can follow the below approach as I think setState doesn't handle nested updates.
var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})
The idea is to create a dummy object perform operations on it and then replace the component's state with the updated object
Now, the spread operator creates only one level nested copy of the object. If your state is highly nested like:
this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}
You could setState using spread operator at each level like
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
However the above syntax get every ugly as the state becomes more and more nested and hence I recommend you to use immutability-helper package to update the state.
See this answer on how to update state with immutability helper.
Nested State in React is wrong design
Read this excellent answer.
Reasoning behind this answer:
React's setState is just a built-in convenience, but you soon realise that it has its limits. Using custom properties and intelligent use of forceUpdate gives you much more. eg:
class MyClass extends React.Component { myState = someObject inputValue = 42 ...
MobX, for example, ditches state completely and uses custom observable properties.
Use Observables instead of state in React components.
There is another shorter way to update whatever nested property.
this.setState(state => {
state.nested.flag = false
state.another.deep.prop = true
return state
})
this.setState(state => (state.nested.flag = false, state))
note: This here is Comma operator ~MDN, see it in action here (Sandbox).
It is similar to (though this doesn't change state reference)
this.state.nested.flag = false
this.forceUpdate()
For the subtle difference in this context between forceUpdate
and setState
see the linked example.
Of course this is abusing some core principles, as the state
should be read-only, but since you are immediately discarding the old state and replacing it with new state, it is completely ok.
Even though the component containing the state will update and rerender properly (except this gotcha), the props will fail to propagate to children (see Spymaster's comment below). Only use this technique if you know what you are doing.
For example, you may pass a changed flat prop that is updated and passed easily.
render(
//some complex render with your nested state
<ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)
Now even though reference for complexNestedProp did not change (shouldComponentUpdate)
this.props.complexNestedProp === nextProps.complexNestedProp
the component will rerender whenever parent component updates, which is the case after calling this.setState
or this.forceUpdate
in the parent.
Using nested state and mutating the state directly is dangerous because different objects might hold (intentionally or not) different (older) references to the state and might not necessarily know when to update (for example when using PureComponent
or if shouldComponentUpdate
is implemented to return false
) OR are intended to display old data like in the example below.
Imagine a timeline that is supposed to render historic data, mutating the data under the hand will result in unexpected behaviour as it will also change previous items.
Anyway here you can see that Nested PureChildClass
it not rerendered due to props failing to propagate.
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);
Two other options not mentioned yet: