From 0cac32d60dd4482b27fe8a54dffbabceb22c6272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 13 May 2025 15:20:59 -0400 Subject: [PATCH] [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. --- .../react-reconciler/src/ReactFiberRootScheduler.js | 11 ++++++++++- packages/react-reconciler/src/ReactFiberWorkLoop.js | 10 +--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberRootScheduler.js b/packages/react-reconciler/src/ReactFiberRootScheduler.js index 4dfa1975a2..9fc6728cf9 100644 --- a/packages/react-reconciler/src/ReactFiberRootScheduler.js +++ b/packages/react-reconciler/src/ReactFiberRootScheduler.js @@ -78,6 +78,7 @@ import { resetNestedUpdateFlag, syncNestedUpdateFlag, } from './ReactProfilerTimer'; +import {peekEntangledActionLane} from './ReactFiberAsyncAction'; // A linked list of all the roots with pending work. In an idiomatic app, // there's only a single root, but we do support multi root apps, hence this @@ -647,7 +648,15 @@ export function requestTransitionLane( // over. Our heuristic for that is whenever we enter a concurrent work loop. if (currentEventTransitionLane === NoLane) { // All transitions within the same event are assigned the same lane. - currentEventTransitionLane = claimNextTransitionLane(); + const actionScopeLane = peekEntangledActionLane(); + currentEventTransitionLane = + actionScopeLane !== NoLane + ? // We're inside an async action scope. Reuse the same lane. + actionScopeLane + : // We may or may not be inside an async action scope. If we are, this + // is the first update in that scope. Either way, we need to get a + // fresh transition lane. + claimNextTransitionLane(); } return currentEventTransitionLane; } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 95285c936e..c62691aa1f 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -356,7 +356,6 @@ import { requestTransitionLane, } from './ReactFiberRootScheduler'; import {getMaskedContext, getUnmaskedContext} from './ReactFiberContext'; -import {peekEntangledActionLane} from './ReactFiberAsyncAction'; import {logUncaughtError} from './ReactFiberErrorLogger'; import { deleteScheduledGesture, @@ -779,14 +778,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { transition._updatedFibers.add(fiber); } - const actionScopeLane = peekEntangledActionLane(); - return actionScopeLane !== NoLane - ? // We're inside an async action scope. Reuse the same lane. - actionScopeLane - : // We may or may not be inside an async action scope. If we are, this - // is the first update in that scope. Either way, we need to get a - // fresh transition lane. - requestTransitionLane(transition); + return requestTransitionLane(transition); } return eventPriorityToLane(resolveUpdatePriority());