React: Gotcha when using state hooks and async callbacks

Created on 6 Mar 2019  Â·  3Comments  Â·  Source: facebook/react

Do you want to request a feature or report a bug?

Maybe not exactly a bug, but it certainly was surprising behavior when I ran into it.

What is the current behavior?

When using a useState hook, it is preferable (and even recommended in the docs about hooks) to use an individual useState hook for each atomic piece of state your component may contain.

This is all well and good, except that setStates are not batched in async code. I was writing a component that used a few different pieces of state, but had some pieces which all needed to be set at more or less the same time, and as a response to an asynchronous action.

I set both pieces of state, and when I went to test it the whole page crashed. It then took me several minutes of debugging to realize why not all of my state was set, despite my handler looking sound: the component was rerendered during the setState call.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:

This is a contrived example, but you can imagine how a real asynchronous method like a web request or other promise-based api could easily lead to the issue: https://codesandbox.io/s/zk1kwlmp4p

In my exact case, a was actually a currentPage string, and b was an object which a child component needed in order to render, which was fetched asynchronously and passed down.

What is the expected behavior?

I'm not here to say that this is necessarily a bug per se, since this has also been true for class components for a long time and hasn't really been an issue. I've been working in react for two years and have never had a problem with class components and async code, for example.

I'm also well aware of possible workarounds, like storing both fields in the same state object, or reordering the setStates such that the render doesn't switch to the new "mode" until the required state has been set. The first simple seems like it's non-idiomatic for hooks, and as for the second, one could imagine that it would become complicated once there are more modes, and the inter-dependencies between different pieces of state get messier.
I could also use the unstable batched updates api, but I don't like the idea of depending on an unstable api, especially in code where I can't easily insulate myself from breaking changes in the api down the line.

I'd just like to point out that this was what to me seemed like a very natural way to write the component, and that the crash caught me entirely by surprise.

I know that it has been mentioned elsewhere that react may move to always batching setStates, and I think this gotcha may increase the impetus to go ahead and do so.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

Any version since hooks were released, independent of browser or operating system.

Most helpful comment

In the future, Concurrent Mode will batch by default. But it will take a while for it to become stable and for you to be possible to use it.

This problem isn't specific to Hooks. Although it's less prominent in classes because you setState twice less commonly.

The idiomatic solution to this is useReducer. In general, when multiple things tend to be set together, that's a sign useReducer would fit well.

All 3 comments

You probably should use useReducer for complex state management. It specifically covers situations of multiple setState calls on the same state object.

I could also use the unstable batched updates api

This is a viable short term solution IMO. The unstable_batchedUpdates can be used to group multiple set-states together into a single update. (This is what React does automatically inside of e.g. DOM event handlers.) – https://codesandbox.io/s/187om42o8l

Here's a related SO post from Dan:
https://stackoverflow.com/questions/48563650/does-react-keep-the-order-for-state-updates/48610973#48610973

You could also group related state into a single item (either with useReducer or by making the state an object with multiple keys). For example, in this sandbox I store a tuple in state (rather than storing both values separately).

In the future, Concurrent Mode will batch by default. But it will take a while for it to become stable and for you to be possible to use it.

This problem isn't specific to Hooks. Although it's less prominent in classes because you setState twice less commonly.

The idiomatic solution to this is useReducer. In general, when multiple things tend to be set together, that's a sign useReducer would fit well.

Was this page helpful?
0 / 5 - 0 ratings