JavaScript

JavaScript

Made by DeepSource

Avoid direct mutation of this.state JS-0444

Bug risk
Major
react

The only good place to assign this.state is in an ES6 class component constructor.

It is not recommended to mutate this.state directly, as calling setState() afterward may replace the mutation. You should treat this.state as if it were immutable.

Problems with state mutations in react

React docs mention to never change this.state directly; instead, always use this.setState for any state updates. These are two main reasons to do this:

  • setState works in batches, which means one cannot expect the setState to do the state update immediately; it is an asynchronous operation, so the state changes may happen at a later point in time, which means manually mutating state may get overridden by setState.

  • It can affect the performance. When using pure component or shouldComponentUpdate, they will do a shallow comparison using the === operator. However, if one mutates the state, the object reference will still be the same, so the comparison would fail.

Recommended Approach

Suppose, we have a function updateState() which has a mutation problem. Here are the different approaches to fix this:

Problematic code:

updateState(event) {
 const {name, value} = event.target;
 let user = this.state.user; // this is a reference, not a copy...
 user[name] = value; // so this mutates state ?
 return this.setState({user});
}
  1. Use Object.assign

Object.assign creates a copy of an object. The first parameter is the target, then specify one or more parameters for properties you’d like to tack on.

Fixed code:

updateState(event) {
 const {name, value} = event.target;
 let user = Object.assign({}, this.state.user);
 user[name] = value;
 return this.setState({user});
}
  1. Use the Spread operator (...)

You can assume spread operator as a shorthand syntax for Object.assign() but it overrides the value of the same key which comes first by the value that comes later.

Fixed code:

updateState(event) {
 const {name, value} = event.target;
 let user = {...this.state.user, [name]: value};
 this.setState({user});
}

Problems to look out for

Ther's also a case when your react application state has nested objects e.g.,

let user = {
  profile:{
    address:{
      city: ‘London’
    }
  }
}

If there's a need to modify city from London to Newyork immutably, it could be done like this:

{
...state,
  user:{
    ...state.user,
    profile:{
      ...state.user.profile,
      address:{
        ...state.user.profile.address,
        city:’Newyork’
      }
    }
  }
}

So, it is recommended to keep react state as flat as possible, also consider using Immutable.js or immutability-helper

Bad Practice

var Hello = createReactClass({
  componentDidMount: function() {
    this.state.name = this.props.name.toUpperCase();
  },
  render: function() {
    return <div>Hello {this.state.name}</div>;
  }
});

class Hello extends React.Component {
  constructor(props) {
    super(props)

    // Assign at instance creation time, not on a callback
    doSomethingAsync(() => {
      this.state = 'bad';
    });
  }
}

Recommended

var Hello = createReactClass({
  componentDidMount: function() {
    this.setState({
      name: this.props.name.toUpperCase();
    });
  },
  render: function() {
    return <div>Hello {this.state.name}</div>;
  }
});

class Hello extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      foo: 'bar',
    }
  }
}

References