this.setState isn't merging states as I would expect
Solution 1:
I think setState()
doesn't do recursive merge.
You can use the value of the current state this.state.selected
to construct a new state and then call setState()
on that:
var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
I've used function _.extend()
function (from underscore.js library) here to prevent modification to the existing selected
part of the state by creating a shallow copy of it.
Another solution would be to write setStateRecursively()
which does recursive merge on a new state and then calls replaceState()
with it:
setStateRecursively: function(stateUpdate, callback) {
var newState = mergeStateRecursively(this.state, stateUpdate);
this.replaceState(newState, callback);
}
Solution 2:
Immutability helpers were recently added to React.addons, so with that, you can now do something like:
var newState = React.addons.update(this.state, {
selected: {
name: { $set: 'Barfoo' }
}
});
this.setState(newState);
Immutability helpers documentation.
Solution 3:
Since many of the answers use the current state as a basis for merging in new data, I wanted to point out that this can break. State changes are queued, and do not immediately modify a component's state object. Referencing state data before the queue has been processed will therefore give you stale data that does not reflect the pending changes you made in setState. From the docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
This means using "current" state as a reference in subsequent calls to setState is unreliable. For example:
- First call to setState, queuing a change to state object
- Second call to setState. Your state uses nested objects, so you want to perform a merge. Before calling setState, you get current state object. This object does not reflect queued changes made in first call to setState, above, because it's still the original state, which should now be considered "stale".
- Perform merge. Result is original "stale" state plus new data you just set, changes from initial setState call are not reflected. Your setState call queues this second change.
- React processes queue. First setState call is processed, updating state. Second setState call is processed, updating state. The second setState's object has now replaced the first, and since the data you had when making that call was stale, the modified stale data from this second call has clobbered the changes made in the first call, which are lost.
- When queue is empty, React determines whether to render etc. At this point you will render the changes made in the second setState call, and it will be as though the first setState call never happened.
If you need to use the current state (e.g. to merge data into a nested object), setState alternatively accepts a function as an argument instead of an object; the function is called after any previous updates to state, and passes the state as an argument -- so this can be used to make atomic changes guaranteed to respect previous changes.