From 8ca193d044e1518d6ef1408139fd15b9d06ed130 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 13 May 2025 11:34:29 -0400 Subject: [PATCH] Stash the entangled async action lane on currentEventTransitionLane 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 f7c26580bb95a..108c6ff849eb1 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 @@ -645,7 +646,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 95285c936eb93..c62691aa1f8c4 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());