-
Notifications
You must be signed in to change notification settings - Fork 47.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: State updates are reordered across await in an effect in React 18. #30890
Comments
To fix this issue, you can leverage the functional form of setState, which allows React to ensure that state updates are applied in order. Here’s how you can modify your code
|
OverviewThanks for reporting and the detailed sandbox. This is a bug fixed in React 19. To show what's happening, I updated your sandbox with some profiling markers and delays: https://codesandbox.io/p/sandbox/react-dev-forked-rt7rdr?file=%2FLibraryReports.js%3A8%2C1 React 18 TimelineHere's the timeline for 18: Here you can see that the first update is scheduled (as a default priority), then the microtask is scheduled (for the await). The microtask is fired by the browser, which schedules the second update (which is sync priority). In React 18, we did not batch default and sync lane updates together even though they are both sync updates. Since the sync priority update is given higher priority than the default priority, it is flushed and committed first (that's why you see a flash of this state). Then we render the default priority update. Since it was interrupted by the lower priority update, we rebase it on top of the sync update, and render the result of both state updates together (that's why you see both state updaters run in the last render). So the final committed state is correct (as long as you're using a reducer / state updater), but there's a flash of incorrect state in between. React 19 TimelineHere's the timeline for 19: Here you can see both updates are scheduled the same as before, but now there is a single render with both updates included. This avoids the flash of intermediate state. Shout out @tyao1 and @acdlite for working on this with for 19: #25700 |
Thank you very much for the detailed explanation of what's going on under the hood, and the confirmation that this is indeed fixed in React 19. Do you happen to know if there will be a React 18 release that includes this fix? |
The changes to fix this are significant and not something we can backport. So unfortunately this fix will be 19 only. |
No problem. Thanks for the info. |
React version: 18.3.1
Steps To Reproduce
Link to code example: https://codesandbox.io/p/sandbox/react-dev-forked-9m77xd
The current behavior
React will render with the second state update first ("bar"), and then re-render with the first update ("foo"). This only happens when the awaited function returns immediately. If the awaited promise resolves at a later time, the state is updated in the expected order.
In the example, you can observe this happening by clicking the button and looking at the console ("BUG RENDERED!!!😱😱😱"), or by seeing a brief flash of red on the component as it renders inaccurately. The console message triggers every time, the red flash only occurs occasionally. In our actual app we're seeing the render occur every time.
If you adjust the example to instead call
await optionalDelay(true)
, the state updates will be applied in order and the component will flash green.The expected behavior
The state updates should be applied in the order as specified in the code. This was the behavior in React 16, which can be tested in the sandbox.
This appears to be fixed in React 19.0.0-rc.0.
Previously reported in:
The text was updated successfully, but these errors were encountered: