React.js: setState overwriting, not merging

Solution 1:

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:

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:

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