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
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.
Most helpful comment
It's likely because you're mutating the current
state
object in yourtoggleSetting
methodSince 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 treatstate
as immutableReact 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