React.js: setState overwriting, not merging

JavascriptReactjs

Javascript 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 :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDanVView Question on Stackoverflow
Solution 1 - JavascriptMichelle TilleyView Answer on Stackoverflow
Solution 2 - JavascriptptimView Answer on Stackoverflow
Solution 3 - JavascriptMetalstormView Answer on Stackoverflow