165

I know that React may perform state updates asynchronously and in batch for performance optimization. Therefore you can never trust the state to be updated after having called setState. But can you trust React to update the state in the same order as setState is called for

  1. the same component?
  2. different components?

Consider clicking the button in the following examples:

1. Is there ever a possibility that a is false and b is true for:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Is there ever a possibility that a is false and b is true for:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Keep in mind that these are extreme simplifications of my use case. I realize that I can do this differently, e.g. updating both state params at the same time in example 1, as well as performing the second state update in a callback to the first state update in example 2. However, this is not my question, and I am only interested in if there is a well defined way that React performs these state updates, nothing else.

Any answer backed up by documentation is greatly appreciated.

darksmurf
  • 3,029
  • 5
  • 20
  • 31
  • 1
    See this: https://stackoverflow.com/a/36087156/3776299 – helb Feb 01 '18 at 13:17
  • 4
    it doesn't seem a senseless question, you can also ask that question on github issues of react page, dan abramov is usually quite helpful there. When I had such tricky questions I would ask and he'd respond. Bad is that those kind of issues are not shared publicly like in official docs (so that others can also access it easily). I also feel React official docs lacks extensive coverage of some topics like the topic from your question, etc. – gmoniava Feb 01 '18 at 13:21
  • 1
    For example take this: https://github.com/facebook/react/issues/11793, I believe stuff discussed in that issue would be helpful for many developers but that stuff is not on the official docs, because FB folks consider that advanced. Same is about other things possibly. I would think an official article titled something like 'state management in react in depth' or 'pitfalls of state management' which explore all corner cases of state management like in your question would be not bad. maybe we can push FB developers to extend documentation with such stuff :) – gmoniava Feb 01 '18 at 13:26
  • Thre is a link to a great article on medium in my question. It should cover 95 % of state use cases. :) – Michal Feb 01 '18 at 14:08
  • 2
    @Michal but that article still doesn't answer this question IMHO – gmoniava Feb 01 '18 at 14:15
  • @Giorgi Moniava Why is that? Your `handleClick` method will be batched so you don't have to be afraid that you will have `a=true` and `b=false`. – Michal Feb 01 '18 at 15:23

4 Answers4

387

I work on React.

TLDR:

But can you trust React to update the state in the same order as setState is called for

  • the same component?

Yes.

  • different components?

Yes.

The order of updates is always respected. Whether you see an intermediate state "between" them or not depends on whether you're inside in a batch or not.

Currently (React 16 and earlier), only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.

In future versions (probably React 17 and later), React will batch all updates by default so you won't have to think about this. As always, we will announce any changes about this on the React blog and in the release notes.


The key to understanding this is that no matter how many setState() calls in how many components you do inside a React event handler, they will produce only a single re-render at the end of the event. This is crucial for good performance in large applications because if Child and Parent each call setState() when handling a click event, you don't want to re-render the Child twice.

In both of your examples, setState() calls happen inside a React event handler. Therefore they are always flushed together at the end of the event (and you don't see the intermediate state).

The updates are always shallowly merged in the order they occur. So if the first update is {a: 10}, the second is {b: 20}, and the third is {a: 30}, the rendered state will be {a: 30, b: 20}. The more recent update to the same state key (e.g. like a in my example) always "wins".

The this.state object is updated when we re-render the UI at the end of the batch. So if you need to update state based on a previous state (such as incrementing a counter), you should use the functional setState(fn) version that gives you the previous state, instead of reading from this.state. If you're curious about the reasoning for this, I explained it in depth in this comment.


In your example, we wouldn't see the "intermediate state" because we are inside a React event handler where batching is enabled (because React "knows" when we're exiting that event).

However, both in React 16 and earlier versions, there is yet no batching by default outside of React event handlers. So if in your example we had an AJAX response handler instead of handleClick, each setState() would be processed immediately as it happens. In this case, yes, you would see an intermediate state:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

We realize it's inconvenient that the behavior is different depending on whether you're in an event handler or not. This will change in a future React version that will batch all updates by default (and provide an opt-in API to flush changes synchronously). Until we switch the default behavior (potentially in React 17), there is an API you can use to force batching:

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

Internally React event handlers are all being wrapped in unstable_batchedUpdates which is why they're batched by default. Note that wrapping an update in unstable_batchedUpdates twice has no effect. The updates are flushed when we exit the outermost unstable_batchedUpdates call.

That API is "unstable" in the sense that we will remove it when batching is already enabled by default. However, we won't remove it in a minor version, so you can safely rely on it until React 17 if you need to force batching in some cases outside of React event handlers.


To sum up, this is a confusing topic because React only batches inside event handlers by default. This will change in future versions, and the behavior will be more straightforward then. But the solution is not to batch less, it's to batch more by default. That's what we're going to do.

Community
  • 1
  • 1
Dan Abramov
  • 241,321
  • 75
  • 389
  • 492
  • 2
    One way to *"always get the order right"* is to create a temporary object, assign the different values (e.g `obj.a = true; obj.b = true`) and then at the end just do `this.setState(obj)`. This is safe regardless if you are inside an event handler or not. Might be a neat trick if you often find yourself making the mistake of setting the state several times outside of event handlers. – Chris Feb 12 '18 at 13:43
  • So we cannot actually rely on batching to be limited to just one event handler - as you made clear, at least because this will not be the case soon. Then we are supposed to use setState with updater function to get access to most recent state, right? But what if I need to use some state.filter to make an XHR to read some data and then put those into state? Looks like I'll have to put an XHR with deferred callback (and hence a side effect) into an updater. Is that considered a best practice, then? – Maksim Gumerov Feb 22 '18 at 11:35
  • 1
    And by the way that also means we should not read from this.state at all; the only reasonable way to read some state.X is to read it in updater function, from its argument. And, writing to this.state is also unsafe. Then why allow access to this.state at all? Those should maybe made standalone questions but mostly I am just trying to understand if I got the explanation right. – Maksim Gumerov Feb 22 '18 at 11:39
  • Doesn't React also batch updates in lifecycle methods? If so, we can update the answer to mention this? – Oliver Joseph Ash Mar 05 '18 at 14:44
  • 16
    this answer should be added to reactjs.org documentation – Deen John Mar 10 '18 at 02:53
  • What part of it you want to see in documentation? Regarding order within same component - I believe I saw it mentioned somewhere. Across components - well, maybe. About how batching works at present - definitely not, because Dan here clearly stated this is something not to design against. About how the batching will work in the future - well, it's clear enough that if we cannot rely on batching be limited to one event (and we cannot because it's not documented), then we have to design against potentially much larger batches spanning across events. – Maksim Gumerov Mar 14 '18 at 05:45
  • The order of `setState` is preserved for the same `state` but if you fire two `setState` for two different parent's state, it is not sure which one will end up (and so be re-rendered) first – ClementWalter Mar 26 '18 at 19:10
  • Random question - if you had zones or other context tracking for promises - would React use them to do this or would that not help at all? – Benjamin Gruenbaum May 18 '18 at 13:26
  • Does this hold true for functional setState? – Merlin -they-them- Aug 13 '18 at 14:27
  • `if you need to update state based on a previous state ... use the functional setState(fn) version that gives you the previous state, instead of reading from this.state` Do I need to use an updater function even if setState is not batched? E.g. when I am reading the result of an AJAX call and adding new records coming from the response to the records in the state? My guess is that as `setState` is called within an AJAX callback and therefore is not batched, I can directly read `this.state` and not use an updater function even if my state update depends on the previous state? What do you think? – tonix Jul 12 '19 at 10:46
  • 2
    Could you please clarify in this post if "React event handler" includes `componentDidUpdate` and other lifecycle callbacks as of React 16? Thanks in advance! – Ivan Feb 14 '20 at 11:48
  • Now, in experimental concurrent mode, all `setState` are batched by default: https://reactjs.org/docs/concurrent-mode-adoption.html – Tina Chen Feb 18 '21 at 13:05
8

This is actually a quite interesting question but the answer shouldn't be too complicated. There is this great article on medium that has an answer.

1) If you do this

this.setState({ a: true });
this.setState({ b: true });

I don't think that there will be a situation where a will be true and b will be false because of batching.

However, if b is dependent on a then there indeed might be a situation where you wouldn't get the expected state.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

After all the above calls are processed this.state.value will be 1, not 3 like you would expect.

This is mentioned in the article: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

This will give us this.state.value === 3

Michal
  • 4,323
  • 5
  • 27
  • 52
  • What if `this.state.value` is updated both in event handlers (where `setState` is batched) and AJAX callbacks (where `setState` is **not** batched). In event handlers I would use the `updater function` to always be sure that I update state using the currently updated state provided by the function. Should I use `setState` with an updater function inside the code of the AJAX callback even if I know that it is not batched? Could you please clarify the usage of `setState` within an AJAX callback with or without using an updater function? Thank you! – tonix Jul 12 '19 at 12:23
  • @Michal, hi Michal just wanted to ask mere question, is it true that if we have this.setState({ value: 0}); this.setState({ value: this.state.value + 1}); the first setState will be ignored and only the second setState will be executed? – Dickens Aug 30 '19 at 14:37
  • @Dickens I believe both `setState` would be executed but the last one would win. – Michal Aug 30 '19 at 16:07
3

Multiple calls during the same cycle may be batched together. For example, if you attempt to increment an item quantity more than once in the same cycle, that will result in the equivalent of:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html

Mosè Raguzzini
  • 12,776
  • 26
  • 36
3

as in doc

setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.

it will preform the change as in queue (FIFO : First In First Out) the first call will be first to preform

Ali Faris
  • 13,532
  • 6
  • 31
  • 54
  • hi Ali, just wanted to ask mere question, is it true that if we have this.setState({ value: 0}); this.setState({ value: this.state.value + 1}); the first setState will be ignored and only the second setState will be executed? – Dickens Aug 30 '19 at 14:38