preface

Why is setState() in React asynchronous? I used to think setState() was synchronous, but was confused to learn that it was asynchronous and even expected React to have a setStateSync() API. Michel Weststrate, the author of MobX, also has this doubt. He thinks that the answers often heard are easy to refute, and thinks that this may be a historical burden, so he opened an issue to ask the real reason. The issue eventually got a response from React core member Dan Abramov, who made it clear that it wasn’t a historical baggage, but a well-thought-out design.

Note: this post is based on Dan’s response, but is not a translation. I left out a lot of less important stuff, Dan’s full response is here.

The body of the

There is no obvious answer as to why setState() is asynchronous, and every solution has its trade-offs. React was designed with the following considerations in mind:

First, to ensure internal consistency

First, I think we can all agree that delaying and batching rerenders is beneficial and important for performance optimization, regardless of whether setState() is synchronous or asynchronous. So even if you update state synchronously, you don’t know props until the parent re-renders.

The React design ensures that the objects (state, props, refs) provided by React behave and behave identically. Why is this important? Dan gave an example:

Assuming state is updated synchronously, the following code works as expected:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2
Copy the code

However, at this point you need to promote the state to the parent component for multiple sibling components to share:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Do the same thing in the parent component
Copy the code

It should be noted that this is a very common refactoring in React applications and happens almost every day.

However, the following code does not work as expected:

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
Copy the code

This is because in the synchronous model, although this.state is updated immediately, this.props is not. And we can’t update this.props immediately without re-rendering the parent component. If you wanted to update this.props immediately (i.e., rerender the parent component immediately), you would have to give up batch processing (which, depending on the situation, could have a significant performance degradation).

In React, both this.state and this.props are updated asynchronously. In the example above, 0 is printed before and after refactoring. This will make the state lift safer.

In the end, Dan concluded that the React model preferred to ensure internal consistency and state-enhanced security, rather than always pursuing code simplicity.

Second, performance optimization

We generally assume that state updates are applied in a given order, whether state is synchronous or asynchronous. But this is not necessarily the case.

React assigns different priorities to different setState() calls depending on the call source. Call sources include event handling, network requests, animations, and so on.

Dan held up another chestnut. Suppose you are in a chat window and you are entering a message. The setState() call in the TextBox component needs to be applied immediately. However, a new message is received while you are typing. It might be better to delay rendering the new MessageBubble component to make your input flow more smoothly, rather than immediately rendering the new MessageBubble component to block the thread and cause your input to jitter and delay.

If you assign low priority to certain updates, you can break their rendering into blocks of milliseconds and the user won’t notice.

Third, more possibilities

Dan concluded by saying that asynchronous updates are not just about performance optimization, but a fundamental shift in what the React component model can do.

Dan held up a chestnut. Suppose you navigate from one page to another, usually you need to show a loading animation and wait for the new page to render. But if the navigation is very fast, blinking the loading animation will degrade the user experience.

Wouldn’t it be better if you simply called setState() to render a new page and React “behind the scenes” started rendering the new page? Imagine that without you having to write any coordination code, if the update takes a long time, you can show a loading animation, otherwise let React perform a seamless switch when the new page is ready. In addition, the old pages can still interact while waiting, but if it takes longer, you must show a loading animation.

It turns out that a few life cycle adjustments based on the current React model can actually make this happen. @acdLite has been working on this feature for a few weeks and will soon release an RFC (also rowing!) .

It is important to note that asynchronously updating state is a prerequisite for this scenario to be possible. If you update state synchronously, there is no way to render the new page behind the scenes while keeping the old page interactive. Separate status updates between them conflict.

“I hope we can convince you in the next few months and you’ll appreciate the flexibility of the React model,” Dan concluded with Michel. As I understand it, at least part of this flexibility is due to the asynchronous updating of state.