Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent this.state to be used with setState

The reference states:

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

So it is considered a mistake in React to use this.state values together with setState because setState is asynchronous and may result in updating the state with wrong values (a demo):

// destructured
const { state, setState } = this;
setState({ foo: state.foo });

// destructured
const { foo } = this.state;
setState({ foo });

// undestructured
this.setState({ foo: this.state.foo });

While this would be proper way to update the state (a demo):

// destructured
this.setState(({ foo }) => ({ foo }));

// undestructured
this.setState(state => ({ foo: state.foo }));

Is there ESLint rule or other way to prevent some or all of these cases where this.state can be misused?

I assume it may be hard but possible to solve this case with static analysis.

like image 744
Estus Flask Avatar asked Oct 08 '18 09:10

Estus Flask


2 Answers

eslint-plugin-react will do this check with react/no-access-state-in-setstate rule

This rule should prevent usage of this.state inside setState calls. Such usage of this.state might result in errors when two state calls are called in batch and thus referencing old state and not the current state.

like image 89
skyboyer Avatar answered Sep 29 '22 08:09

skyboyer


If you use:

// destructured
const { state, setState } = this;
setState({ foo: state.foo });

Eslint still warns you because of state.foo (accessing property of an object). To avoid this, you can define like:

// destructured
const { state: { foo }, setState } = this;
setState({ foo });

But if you use:

// undestructured
this.setState({ foo: this.state.foo });

Then, ESLINT will warn you to use destructuring syntax like:

const { foo } = this.state
this.setState({foo})

Note: since foo is the variable name to update and the name matches, we can use just {foo} and is same as {foo: foo}.


Also, and however, I prefer to use this.setState() syntax rather than destructuring to this. Because, in any application, we use this whenever necessary. And using const { ... } = this seems to be confusing if we look between the code when we see setState rather than this.setState. Thinking as of third developer.


From the comments, you wanted to update the state one after another, then you should be using callback like:

onClick = () => {
   this.setState({ foo: 'Bar' }, () => {
      this.setState({ foo: this.state.foo + '!' });
   });
}

Now, you'll be able to see the changes to Hello Bar! in your demo.

If you use setState like this:

onClick = () => {
   this.setState({ foo: 'Bar' })
   this.setState({ foo: this.state.foo + '!' });
   // obviously, better to use updater syntax though.
}

Then the first setState will be overridden by the last one. And you'll get the changes to Hello Foo! in your demo.

Also, the documentation states the same. The updater syntax is just a handy method but results the same exactly as without the updater syntax. The most important role is only with its callback syntax. The callback syntax is used so that you can access the updated state right after its updates.


To know more about destructuring syntax. You may follow my another post where, you can find detailed information and some links that will be a lot to be familiar.

like image 23
Bhojendra Rauniyar Avatar answered Sep 29 '22 08:09

Bhojendra Rauniyar