React.js: setState overwriting, not merging
JavascriptReactjsJavascript Problem Overview
I'm quite new to React.JS and I am in the process of experimenting by building a masonry-style layout.
I render each element to the DOM, then I need to loop over each item and apply x and y positions based on the preceding elements.
The initial model looks like this:
[ { "title": "The Forrest", "description": "some cool text", "imgSmallSrc": "/img/img4-small.jpg", "imgAlt": "Placeholder image", "tags": [ "Design", "Mobile", "Responsive" ],
"date": 1367154709885,
"podStyle": {
"width": 253
}
}
]
(I've only shown one item to keep things short).
Once I complete the loop and have my x and y data I want to apply this to the podStyle
object. I call setState()
with the following data:
[ { "podStyle": { "x": 0, "y": 0, "height": 146, "width": 253 } }]
This seems to remove all current data from the model and leave me with just the podStyle
data. Am I misunderstanding how this merge works?
Thanks in advance for any help!
Javascript Solutions
Solution 1 - Javascript
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/
Solution 2 - Javascript
>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
and this.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 argument
>
> https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Solution 3 - Javascript
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 :)