React hooks gotchas: setState in async useEffect
🧶
UPDATE: The issue mentioned in this post will be addressed in React v18: https://github.com/reactwg/react-18/discussions/21
Let’s face it, hooks have been wandering around for a while now. Yet it feels like I discover new stuff every day. Today, I’d like to share a new discovery because it is very far from obvious − to say the least!
Small recap: an async effect
An async effect is an effect calling a promise and setting some state based on that promise.
With an example − Fetching a user
First approach
The problem
Using this approach, the <App />
component will render 4 times:
- Render 1: initial mount
- Render 2:
setPending(true)
in theuseEffect()
causes the second render - Render 3:
setPending(false)
in thefetchUser().then()
- Render 4:
setUser(fetchedUser)
in thefetchUser().then()
The problem is: in fetchedUser().then()
, we triggered two setState
s, and instead of having one render as expected, we have one render per setState
call.
Important note:This happens only with async actions (aka promises in
useEffect
). When auseEffect()
does not trigger any async action, thesetState
s are batched properly.
The solution: Grouping states that go together
To reduce the number of renders, we have to reduce setState
calls in async effects. One solution for that is grouping states that are logically bound to each other. Here, the pending
and user
states.
Now it’s fine, we’ll have our 3 renders as expected.
Going further
Abstracting the logic in a custom hook
Let’s create a usePromiseEffect
hook
Let’s use it in our <App />
component:
But wait, that was a whole lotta changes…
OK, but all that to save … 1 render ?!
In this example, yes. But in real life, you often cross the path of way more complex examples. Usually, before initializing forms, you need to fetch a whole lot-a bunch o’ data, and here you’ll save up tons of renders.
All that said, keep in mind that your effects should hold as few responsibilities as possible.
And keep the problem in mind when writing your own effect-style hooks.
Now code safe 🧑💻, and see around 😇
Here’s the codesandbox full demo, you can play with it by un⋅commenting out effects, etc… Cheers!