14

Why there is no async getState function in React ?

Documentation tel us that setState is async. Fine, but that means we can't safely use this.state and we need an async getState as well to respect execution order.

From what I understand we should never use this.state and use a getState function like this :

  getState(callback) {
    this.setState((prevState) => {
      callback(prevState) ;
    });
  }
  ...
  this.getState((curState) => {
    // we now can use the current state safely
  }

Anything that I am missing here in my way of thinking ? Why no such function exists in React ?

-- EDIT --

As a friend of mine told me it was not clear and since I am not convinced but the first answer, let's analyze some piece of code :

simpleFunc() {
    setState({ "counter" : 1 });
    setState({ "counter" : 2 });
    this.state.counter // => no garanty about the value
    getState((curState) => {  // ensure curState.counter is 2 });
}

This simple example shows that we can't use this.state directly in all situations since setState is async.

Here is a counter example where getState could be use : http://codepen.io/Epithor/pen/ZLavWR?editors=0010#0

Short answer: bad pratice, even not sure getState give us the current

The workaround is easy, but the fact that we can factorize some functions and use them without care about the context seems to be interesting, doesn't it ?

So, when many events occurs in a particular order, some events change the state, some read the state : how you can be sure, when an event read the state with this.state to read the good state since all changed are async ?

In fact all is about time :

T     : event 1, change state
T+1ms : event 2, change state
T+2ms : event 3, read state
T+3ms : event 4, change state

As you can't predict when exactly will occurs the setState of event 1 or 2, how you could guarantee that event 3 will really read the state set at event 2 ?

Short answer: events are queued in JS stack whereas state changes are queued in internal React queue. Internal React queue is fully unstacked before giving the hand.

user2668735
  • 1,048
  • 2
  • 18
  • 30
  • Setting and reading the state in the same tick is rather unusual IMO. – Felix Kling Jan 27 '17 at 23:18
  • I am not a JS expert at all, you probably right. I though this would put the async code on the React async queue, so executed later, allowing to "unstack" to be sure there is nothing "waiting". – user2668735 Jan 28 '17 at 00:00

3 Answers3

28

You can definitely use this.state directly in general. You should never mutate state directly (this.state.foo = 0), and instead use setState whenever you want to mutate state.

Usually a setState looks like this:

this.setState({
    foo: 0
})

Then you can safely use this.state.foo eg in your render() function.

There is a caveat however, in that due to the asynchronous nature of setState, you have no guarantee you will have immediate access to this.state after setState has been called.

myFunc(baz) {
    this.setState({
        foo: baz + 1
    })
    console.log(this.state.foo) // not guaranteed
}

Better to do

myFunc(baz) {
    const bazOne = baz + 1
    this.setState({
        foo: bazOne
    })
    console.log(bazOne)
}

Or use the setState functions second parameter, used as a callback executed when the setState operation is finished. In that callback you will have access to the updated state, i.e. this.state:

myFunc(baz) {
    this.setState({ foo: baz + 1 }, () => {
        console.log(this.state.foo) // guaranteed in callback
    });
}

See: https://facebook.github.io/react/docs/react-component.html#setstate

inostia
  • 7,777
  • 3
  • 30
  • 33
  • "You can definitely use this.state directly" : from what I undertood about React framework, not in all situations. I updated my post. – user2668735 Jan 27 '17 at 22:52
  • 1
    Ah I see what you mean. Yes that's correct. In this case, you could use the optional callback parameter from `setState`. But in this case I think it'd be better to store `counter` to a variable if you need access to it in that function since as you say, you have no guarantee. – inostia Jan 27 '17 at 22:58
  • I agree with your answer, but I still need a technical explanation about React/JS async management (queue etc) to deeply understand :) – user2668735 Jan 28 '17 at 00:01
  • 1
    @user2668735: JS has the concept of "run to completion". Events are added to a queue and only processed if there is currently no code running. That means that if you have a series of statements, those will all be executed before the next event is processed. The code would not suddenly be stopped in the middle of the function. In other words, you can be certain that by the time you access `this.state.counter` in your example, the state changes haven't happened yet (assuming that the state is asynchronous). Have a look at https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop . – Felix Kling Jan 28 '17 at 00:03
  • 1
    My guess is that re-rendering the DOM is computationally expensive and so for performance reasons waits until the queue is clear to update state. A synchronous `setState` may cause flickering or other display issues if there isn't enough memory available. See this blog post: https://www.bennadel.com/blog/2893-setstate-state-mutation-operation-may-be-synchronous-in-reactjs.htm – inostia Jan 28 '17 at 03:31
  • Here's a post about it: https://discuss.reactjs.org/t/on-the-async-nature-of-setstate/4423 – inostia Jan 28 '17 at 03:36
  • I understand better, but a last question : when we do a setState, do we have garantees that the state will change BEFORE taking any other event in the JS event stack ? I also added a code pen example to show you a "little" use case about the getState. – user2668735 Jan 28 '17 at 09:45
  • That's pretty bad design IMO, in `smartCopyCounterFunction` basically you're just writing a work-around the issue by setting state to the previous state. It looks like it works, but it's pretty weird. You're also re-rendering twice I believe, in the callback defined in `smartCopyCounterFunction` and in `getState` itself. Besides that, sometimes `setState` is asynchronous and sometimes it's synchronous depending on the context of the calling function, so you can't know if `prevState` is current state or not. Either way, if I saw that code in production I'd probably screech. :) State is state – inostia Jan 28 '17 at 21:26
5

setState is asynchronous, as such you cannot immediatly access the property you changed, HOWEVER, there are cases where you want to perform an action after the state has been changed, in those cases you can do:

...

this.state = {
  x = 1
}

...

this.setState({
  x = 2
}, () => {
  console.log(this.state.x) // outputs 2
});

the setState function is called on a queued tick, so you might queue x number of setStates, they will all get executed on the next tick.

Oscar Franco
  • 5,691
  • 5
  • 34
  • 56
5

It's actually not a bug/problem, but an architecture decision: the state is not intended to be used as a simple property/variable/storage, it's specifically meant to be used for interface/visual state and, as such, don't need to be updated at every call. It uses an internal queue, so if you swap the state many times before rendering, it will actually update it only once with the final value and by the time render method is invoked, it will contain the right value.

If you simply need to store/retrieve information during the execution or between methods that run in the same phase (componentWillReceiveProps and shouldComponentUpdate, for example), you can safely use this.anyProperty as always:

componentWillReceiveProps() {
  this.value = 'guaranteed';
  return true;
}
shouldComponentUpdate() {
  if (this.value === 'guaranteed') {
    console.log('will always return true');
  }
}
componentDidUpdate() {
  this.value = ''; //cleanup
}

In the example above, if you used "setState" there would be no guarantee that the value would always be updated in "shouldComponentUpdate", but it shouldn't matter if you use it for its intended purpose. The state changes are guaranteed to have been flushed by render time, so it should only contain information used in the rendering phase, not transactional/internal data for your objects. You are free to keep using object properties as usual.

diego nunes
  • 2,750
  • 1
  • 14
  • 16