await setState() — Rethinking react’s “asynchronous” state updates.

date
Jan 29, 2024
slug
await-setstate-rethinking-reacts-asynchronous-state-updates
status
Published
tags
react
javascript
webdevelopment
summary
type
Post
 
notion image
As a React newcomer, I initially adopted the practice of using await before the setState function to ensure immediate state updates or when anticipating updated state values later in the function. This approach became the norm in a smaller codebase, offering ease and predictability. However, as our codebase expanded and our React knowledge grew, we discovered documented strategies for achieving updated states, realizing that our reliance on await setState was a suboptimal practice.
We encountered scenarios where await didn't behave as expected, accompanied by warnings like 'await' has no effect on the type of this expression. Investigating further, we found that, despite being “asynchronous” by nature, setState didn't return a promise. This led to confusion about why it initially worked and why writing await was seemingly easier than using React's recommended callbacks.
To unravel the mystery, let’s delve into how await treats a function that doesn't return a promise.
Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.
In the case of setState, which returns nothing (undefined), using await is akin to await Promise.resolve(undefined).
To simplify our case, introducing a wrapper around setState and exploring multiple setState calls to illustrate automatic batching, although it doesn’t really matter if there is one or more setState, batching would work in similar manner.
The body of an async function can be thought of as being split by zero or more await expressions. Top-level code, up to and including the first await expression (if there is one), is run synchronously
Keeping this in mind lets revisit how react batches updates.
React waits until all code in the event handlers has run before processing your state updates
.
As the body of an async function can be split by await expressions, the first split, up to the first await, runs synchronously, allowing React to flush updates before continuing with the second split.
While this approach may seem to provide predictable behavior, it’s crucial to acknowledge that React’s internal update flushing mechanism is not explicitly defined. React may alter how it flushes updates in future versions, focusing on performance enhancements and capabilities, some hints here. Also something worth a read here. Therefore, it’s advisable to avoid relying on await and instead adhere to callbacks for a more predictable and sustainable code behavior.
In conclusion, our journey from await to callbacks in React state updates taught us to prioritize React's recommended practices over seemingly convenient shortcuts, ensuring a more robust and future-proof codebase.

© Naresh Pingale 2021 - 2024