72

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?

1
  • 3
    Try the immutability helpers in React's addons for a declarative way of modifying complex state objects. Commented Jul 23, 2014 at 11:23

3 Answers 3

86

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/

Sign up to request clarification or add additional context in comments.

3 Comments

So I'm pretty sure I'm setting the state as per your second example, however, 'radius' in this case is getting removed. When I'm home tonight I'll have a play around and make sure I'm setting correctly. Thanks for your help Brandon!
@DanV Here is a JSFiddle to demonstrate the code in my answer. jsfiddle.net/BinaryMuse/HW6w5 I also added a button to use the immutability helpers mentioned by ssorallen, which does what you want to do.
Thanks so much for your help Brandon. This definitely sent me in the right direction. I ended up using the $merge helper as my model was slightly different and I was also applying the updates to several children items. Works nicely though.
23

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

2 Comments

I am new to react, but this seems wrong; handleChange should call this.setState( (prevState, props) => ({ ... // use prevState.point }) ) instead.
OMG I can't believe, it works like charm. You saved my day.
4

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

2 Comments

The outer Object.assign isn't really doing anything since the something property you're assigning still refers to the original (although mutated) object. Also, you shouldn't mutate objects in the state directly. It may be OK since you call setState anyway, but it's not clean. Furthermore, you can supply partial updates to setState instead of a full state object.
Thinking about it a little more: it is definitely NOT OK to mutate this.state and then call setState with a reference to this.state. This is because it will break code in shouldComponentUpdate that may compare the next state with the previous state (which will always be equal in this case since both are references to the same object).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.