React hooks gotchas: setState in async useEffect

🧶

Thomas Juster
Level Up Coding

--

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!

So you think you master hooks … ?

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

Problem, what problem ?

Using this approach, the <App /> component will render 4 times:

  • Render 1: initial mount
  • Render 2: setPending(true) in the useEffect() causes the second render
  • Render 3: setPending(false) in the fetchUser().then()
  • Render 4: setUser(fetchedUser) in the fetchUser().then()

The problem is: in fetchedUser().then(), we triggered two setStates, 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 a useEffect() does not trigger any async action, the setStates 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!

Have fun 🤓

--

--