React: this.props and NextProps the same after parent state changes

Created on 25 Jun 2016  路  2Comments  路  Source: facebook/react

I am changing the parent state of a component from a nested child after passing a callback method down through props. This is working as expected, as the state is being changed and my components are updating properly. However, while optimizing my rendering I noticed that for this particular prop settings it ends up being the newest prop in both componentWillReceiveProps and shouldComponentUpdate.

So on my parent component I have a setting object like so:

/*********************************************************************
* in my parent component
*/

class Parent extends React.Component {

constructor(props) {
  super(props);

  this.state = {
    settings: {
      one: true,
      two: true
    }
  };
}

toggleSetting = (key) => {
    const settings = this.state.settings;
    console.log('old settings: ', settings); // Produces expected (old state) results
    settings[key] = !settings[key];
    this.setState({ settings });
    console.log('new settings:', this.state.settings); // Produces expected (new state) results
};

render() {
  return (
    <Child settings={this.state.settings} toggleSetting={this.toggleSetting} />
  );
}

/**********************************************************************
* in my child component
*/

componentWillReceiveProps(nextProps) {
  console.log(this.props.settings, nextProps.settings); // Both new results
}

// this would stop rerendering because it's always true for some reason
shouldComponentUpdate(nextProps) {
  return (this.props.settings.one === nextProps.settings.one); 
}

// in my child render
<GrandChild 
    settings={this.props.settings}
    toggleSetting={this.props.toggleSetting} />

/**********************************************************************
* in my grandchild render
*/

<SomeTag
    settings={this.props.settings}
    onClick={()=>this.props.toggleSetting('one')} />

All behavior works as expected, except for the incoming props and "old" props being the same

Most helpful comment

It's likely because you're mutating the current state object in your toggleSetting method

 const settings = this.state.settings;
    console.log('old settings: ', settings); // Produces expected (old state) results
    settings[key] = !settings[key];

Since you're mutating the current state object and then using that to set the next state, you're getting the same reference in your current and next state. You should always treat state as immutable

toggleSetting = (key) => {
    const settings = this.state.settings;
    this.setState({ 
       settings: !this.state.settings
     });
};

React expects you to treat state as immutable, so doing it this way is always recommended. See
https://facebook.github.io/react/docs/component-api.html#setstate

Notes:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

All 2 comments

It's likely because you're mutating the current state object in your toggleSetting method

 const settings = this.state.settings;
    console.log('old settings: ', settings); // Produces expected (old state) results
    settings[key] = !settings[key];

Since you're mutating the current state object and then using that to set the next state, you're getting the same reference in your current and next state. You should always treat state as immutable

toggleSetting = (key) => {
    const settings = this.state.settings;
    this.setState({ 
       settings: !this.state.settings
     });
};

React expects you to treat state as immutable, so doing it this way is always recommended. See
https://facebook.github.io/react/docs/component-api.html#setstate

Notes:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

@Aweary of course. I would normally do the snippet you just showed but I am trying to change a particular key within an object on state. As it turns out, I am doing this incorrectly in a few places. Had my console.log been 1 line lower, I would have caught that I was changing the original state this way. I often forget that. Anyway I've found the Immutability Helpers which work great. Thanks for the response.

Was this page helpful?
0 / 5 - 0 ratings