React: [Feature] Have setState defer the render but 'apply' the new state immediately

Created on 25 Feb 2017  路  5Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?
Feature the resolves a bug

What is the current behavior?
call to setState defers the internal state update. Thus trying to access this.state on line 27 will not return the state that was set on line 26, because of React's batching algorithm.

Assume we have setup like this:

...
constructor(props) {
    super(props)
    this.state = {
        tabs: [],
        focusedTab: null
    }
}

openNewTab(path) {
    this.setState(extend({}, this.state, {
        tabs: [...this.state.tabs, path]
    }))
    this.focusTab(path)
}

focusTab(path) {
    // Since React didn't actually update `this.state` just yet, `this.state.tabs` that got updated in `openNewTab` before calling this function will get overwritten here as the value of `this.state` is still the old one
    this.setState(extend({}, this.state, {
        focusedTab: path
    }))
}
...

What is the expected behavior?

React should batch just render/reconciliation and let the state change take effect immediately that way the code above would not introduce not so unexpected behavior.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

Latest version, all, all, N/A

Most helpful comment

This is solved by setState form that takes an updater function:

this.setState(prevState => ({
  tabs: [...prevState.tabs, path]
}));

Updaters are guaranteed to receive fresh state, and if you enqueue several of them, they will form a chain, as you would expect.

By the way you never need to manually merge state with existing state. setState() already does that.

Both of these points are documented:

https://facebook.github.io/react/docs/state-and-lifecycle.html#using-state-correctly

All 5 comments

This is solved by setState form that takes an updater function:

this.setState(prevState => ({
  tabs: [...prevState.tabs, path]
}));

Updaters are guaranteed to receive fresh state, and if you enqueue several of them, they will form a chain, as you would expect.

By the way you never need to manually merge state with existing state. setState() already does that.

Both of these points are documented:

https://facebook.github.io/react/docs/state-and-lifecycle.html#using-state-correctly

Thank you for the response, now works as expected.

Just for the sake of clarity, why do we have two ways of updating the state, with Object and updater function. Do they serve different purposes?

By the way you never need to manually merge state with existing state. setState() already does that.

Wasn't aware before, but after you mentioned the culprit is bad d.ts file. And doing this.setState({tabs: [..]}) produced squiggly red lines

Argument of type '{ tabs: any[]; }' is not assignable to parameter of type 'IPaneState'. Property 'mruOrder' is missing in type '{ tabs: any[]; }'

The definition file is installed using typings from this repo which is not always up-to-date (No React.PureComponent)

Speaking of which, any plans of an official definition file for React?

(A special thank you for your great contribution)

why do we have two ways of updating the state, with Object and updater function. Do they serve different purposes?

Updater overload was added later for use cases like this. However using it for simple updates that don't depend on previous state is very verbose. We might find a different API for this later at some point.

Yes, that definition seems wrong. Have you tried using this guide? Does it help? https://www.typescriptlang.org/docs/handbook/react-&-webpack.html

Thank you very much, God of Interwebz
I highly doubt that you've left any *://**docs* unturned

hi, @NeekSandhu , the related code ReactCompositeComponent for more information.

Was this page helpful?
0 / 5 - 0 ratings