@samitouri / QOS-React-1 / commits / 0cac32d60d

[Fiber] Stash the entangled async action lane on currentEventTransitionLane (#33188)

When we're entangled with an async action lane we use that lane instead of the currentEventTransitionLane. Conversely, if we start a new async action lane we reuse the currentEventTransitionLane. So they're basically supposed to be in sync but they're not if you resolve the async action and then schedule new stuff in the same event. Then you end up with two transitions in the same event with different lanes. By stashing it like this we fix that but it also gives us an opportunity to check just the currentEventTransitionLane to see if this event scheduled any regular Transition updates or Async Transitions.

Sebastian Markbåge committed May 13, 2025 at 15:20 UTC 0cac32d60dd4482b27fe8a54dffbabceb22c6272
2 files changed +11 -10
packages/react-reconciler/src/ReactFiberRootScheduler.js
+10 -1
@@ -78,6 +78,7 @@ import {
78 resetNestedUpdateFlag,
79 syncNestedUpdateFlag,
80 } from './ReactProfilerTimer';
81 +import {peekEntangledActionLane} from './ReactFiberAsyncAction';
82
83 // A linked list of all the roots with pending work. In an idiomatic app,
84 // there's only a single root, but we do support multi root apps, hence this
@@ -647,7 +648,15 @@ export function requestTransitionLane(
648 // over. Our heuristic for that is whenever we enter a concurrent work loop.
649 if (currentEventTransitionLane === NoLane) {
650 // All transitions within the same event are assigned the same lane.
650 - currentEventTransitionLane = claimNextTransitionLane();
651 + const actionScopeLane = peekEntangledActionLane();
652 + currentEventTransitionLane =
653 + actionScopeLane !== NoLane
654 + ? // We're inside an async action scope. Reuse the same lane.
655 + actionScopeLane
656 + : // We may or may not be inside an async action scope. If we are, this
657 + // is the first update in that scope. Either way, we need to get a
658 + // fresh transition lane.
659 + claimNextTransitionLane();
660 }
661 return currentEventTransitionLane;
662 }
packages/react-reconciler/src/ReactFiberWorkLoop.js
+1 -9
@@ -356,7 +356,6 @@ import {
356 requestTransitionLane,
357 } from './ReactFiberRootScheduler';
358 import {getMaskedContext, getUnmaskedContext} from './ReactFiberContext';
359 -import {peekEntangledActionLane} from './ReactFiberAsyncAction';
359 import {logUncaughtError} from './ReactFiberErrorLogger';
360 import {
361 deleteScheduledGesture,
@@ -779,14 +778,7 @@ export function requestUpdateLane(fiber: Fiber): Lane {
778 transition._updatedFibers.add(fiber);
779 }
780
782 - const actionScopeLane = peekEntangledActionLane();
783 - return actionScopeLane !== NoLane
784 - ? // We're inside an async action scope. Reuse the same lane.
785 - actionScopeLane
786 - : // We may or may not be inside an async action scope. If we are, this
787 - // is the first update in that scope. Either way, we need to get a
788 - // fresh transition lane.
789 - requestTransitionLane(transition);
781 + return requestTransitionLane(transition);
782 }
783
784 return eventPriorityToLane(resolveUpdatePriority());