2

I may be missing something. I know setState is asynchronous in React, but I still seem to have this question.

Imagine following is a handler when user clicks any of the buttons on my app

1. ButtonHandler()
2. {
3. if(!this.state.flag)
4. {
5.   alert("flag is false");
6. }
7. this.setState({flag:true});
8. 
9. }
  • Now imagine user very quickly clicks first one button then second.
  • Imagine the first time the handler got called this.setState({flag:true}) was executed, but when second time the handler got called, the change to the state from the previous call has not been reflected yet -- and this.state.flag returned false.
  • Can such situation occur (even theoretically)? What are the ways to ensure I am reading most up to date state?
  • I know setState(function(prevState, props){..}) gives you access to previous state but what if I want to only read state like on line 3 and not set it?

2 Answers2

3

As you rightly noted, for setting state based on previous state you want to use the function overload.

I know setState(function(prevState, props){..}) gives you access to previous state

So your example would look like this:

handleClick() {
  this.setState(prevState => {
    return {
      flag: !prevState.flag
    };
  });
}

what if I want to only read state like on line 3 and not set it?

Let's get back to thinking why you want to do this.

If you want to perform a side effect (e.g. log to console or start an AJAX request) then the right place to do it is the componentDidUpdate lifecycle method. And it also gives you access to the previous state:

componentDidUpdate(prevState) {
  if (!prevState.flag && this.state.flag) {
    alert('flag has changed from false to true!');
  }
  if (prevState.flag && !this.state.flag) {
    alert('flag has changed from true to false!');
  }    
}

This is the intended way to use React state. You let React manage the state and don't worry about when it gets set. If you want to set state based on previous state, pass a function to setState. If you want to perform side effects based on state changes, compare previous and current state in componentDidUpdate.

Of course, as a last resort, you can keep an instance variable independent of the state.

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
1

React's philosophy

The state and props should indicate things the components need for rendering. React's render being called whenever the state and props change.

Side Effects

In your case, you're causing a side effect based on user interaction which requires specific timing. In my opinion, once you step out of rendering - you probably want to reconsider state and props and stick to a regular instance property which is synchronous anyway.

Solving the real issue - Outside of React

Just change this.state.flag to this.flag everywhere, and update it with assignment rather than with setState. That way you

If you still have to use .state

You can get around this, uglily. I wrote code for this, but I'd rather not publish it here so people don't use it :)

  • First promisify.
  • Then use a utility for only caring about the last promise resolving in a function call. Here is an example library but the actual code is ~10LoC and simple anyway.
  • Now, a promisified setState with last called on it gives you the guarantee you're looking for.

Here is how using such code would look like:

 explicitlyNotShown({x: 5}).then(() => {
  // we are guaranteed that this call and any other setState calls are done here. 
 });

(Note: with MobX this isn't an issue since state updates are sync).

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504