React.js: setState overwriting, not merging
The merging is shallow, so
this.setState({point})
leaves (ed: this.state.radius) intact, but completely replaces (ed: this.state.point).https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-are-merged
To offer an ES7+ perspective on the answers already given, using transform-object-rest-spread instead of Object.assign()
:
class MyComponent extends React.Component {
state = {
point: {
x: 0,
y: 0,
},
radius: 10,
}
handleChange = () => {
this.setState((prevState, props) => ({
point: {
// rest operator (...) expands out to:
...prevState.point, // x:0, y:0,
y: 1, // overwrites old y
},
// radius is not overwritten by setState
}));
}
render() {
// omitted
}
}
.babelrc (also requires transform-class-properties from babel preset stage 2)
{
"presets": ["es2015", "stage-2", "react"],
"plugins": ["transform-object-rest-spread"],
}
Updated 2018-04-22
As @sheljohn points out (thanks!), referring to this.state
inside setState
is unreliable:
Because
this.props
andthis.state
may be updated asynchronously, you should not rely on their values for calculating the next state....
To fix it, use a second form of
setState()
that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argumenthttps://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Something like:
getInitialState: function() {
return {
something: { x: 0, y: 0 },
blah: 10
};
}
var state = Object.assign(this.state, {
something: Object.assign(this.state.something, { y: 50 }),
});
this.setState(state);
Would be better if it was recursive/deep rather than hard coding the tree, but I will leave that up to the reader :)
If your state is an object:
getInitialState: function() {
return { x: 0, y: 0 };
}
you can use setState
to set individual keys on that object:
this.setState({ x: 1 }); // y still == 0
React does no intelligent merging of your state; for example, this does not work:
getInitialState: function() {
return {
point: { x: 0, y: 0 },
radius: 10
};
}
this.setState({point: {x: 1}});
// state is now == {point: {x: 1}, radius: 10} (point.y is gone)
[Edit]
As mentioned by @ssorallen, you can use the immutability helpers to get the effect you're after:
var newState = React.addons.update(this.state, {
point: { x: {$set: 10} }
});
this.setState(newState);
See this JSFiddle for an example: http://jsfiddle.net/BinaryMuse/HW6w5/