react
react copied to clipboard
Bug: state updates are applied out of order inside useEffect when using Promise.resolve
When two state updates are scheduled within useEffect
, first one directly and second wrapped inside Promise.resolve()
, they will be applied out of order. Same behaviour when using queueMicrotask
instead. setTimeout(x, 0)
doesn't produce the issue.
React version: 18.1.0
This is only happening in Concurrent Mode with createRoot
.
This is based on a real-world scenario arising from certain patterns of usage of react-query
(which internally calls Promise.resolve()
) and useTransition
.
Steps To Reproduce
- Open Codesandbox
- Click the button
- Watch logs in the console
Link to code example: https://codesandbox.io/s/react-state-order-bug-o4izk6?file=/src/App.js:264-265
The current behavior
Console output after button press:
{submitCount: 0, isSubmitScheduled: true}
{submitCount: 1, isSubmitScheduled: true}
{submitCount: 1, isSubmitScheduled: false}
The expected behavior
Console output after button press:
{submitCount: 0, isSubmitScheduled: true}
{submitCount: 0, isSubmitScheduled: false}
{submitCount: 1, isSubmitScheduled: false}
Alternatively, if the updates should have been batched (I'm not sure whether batching should apply here), then the expected output would be):
{submitCount: 0, isSubmitScheduled: true}
{submitCount: 1, isSubmitScheduled: false}
update: now that I've discovered that this applies to microtasks as well, the issue might be related to #24625.
Interesting. The first state update is marked as DefaultLane because flushPassiveEffects sets the event priority; the second state update is outside that call stack so it reads the priority from window.event, which after a microtask is still the click event, leading to it getting treated as a discrete event and thus SyncLane.
how can we escalate the issue to core react developers?
The issue is known, there is no need to escalate. We're working on a fix but it's a significant project so it'll take us a while.
Heloo @Dremora ,
This output {submitCount: 0, isSubmitScheduled: true} {submitCount: 0, isSubmitScheduled: false} {submitCount: 1, isSubmitScheduled: false}
will be generated by creating new useEffect in which we check if isCountUpdateScheduled is true and proceed with the submit count update
**`import React, { useState, useEffect } from "react";
export default function App() { var [submitCount, setSubmitCount] = useState(0); const [isSubmitScheduled, setIsSubmitScheduled] = useState(false); const [isCountUpdateScheduled, setIsCountUpdateScheduled] = useState(false);
const scheduleSubmit = () => setIsSubmitScheduled(true);
useEffect(() => { if (isSubmitScheduled) { setIsSubmitScheduled(false); setIsCountUpdateScheduled(true); } }, [isSubmitScheduled]);
useEffect(() => { if (isCountUpdateScheduled) { setIsCountUpdateScheduled(false); Promise.resolve().then(() => { setSubmitCount((x) => x + 1); }); } }, [isCountUpdateScheduled]);
console.log("render", { submitCount, isSubmitScheduled });
return ( <button type="button" onClick={scheduleSubmit}> Hello ); } `**
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
bump
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!