diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index bc30a468f7d99..fc74f7782a4e2 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -374,6 +374,25 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes { return nextLanes; } +export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number { + const eventTimes = root.eventTimes; + + let mostRecentEventTime = NoTimestamp; + while (lanes > 0) { + const index = pickArbitraryLaneIndex(lanes); + const lane = 1 << index; + + const eventTime = eventTimes[index]; + if (eventTime > mostRecentEventTime) { + mostRecentEventTime = eventTime; + } + + lanes &= ~lane; + } + + return mostRecentEventTime; +} + function computeExpirationTime(lane: Lane, currentTime: number) { // TODO: Expiration heuristic is constant per lane, so could use a map. getHighestPriorityLanes(lane); @@ -606,10 +625,14 @@ export function pickArbitraryLane(lanes: Lanes): Lane { return getHighestPriorityLane(lanes); } -function pickArbitraryLaneIndex(lanes: Lane | Lanes) { +function pickArbitraryLaneIndex(lanes: Lanes) { return 31 - clz32(lanes); } +function laneToIndex(lane: Lane) { + return pickArbitraryLaneIndex(lane); +} + export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) { return (a & b) !== NoLanes; } @@ -648,7 +671,11 @@ export function createLaneMap(initial: T): LaneMap { return new Array(TotalLanes).fill(initial); } -export function markRootUpdated(root: FiberRoot, updateLane: Lane) { +export function markRootUpdated( + root: FiberRoot, + updateLane: Lane, + eventTime: number, +) { root.pendingLanes |= updateLane; // TODO: Theoretically, any update to any lane can unblock any other lane. But @@ -666,6 +693,12 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) { root.suspendedLanes &= higherPriorityLanes; root.pingedLanes &= higherPriorityLanes; + + const eventTimes = root.eventTimes; + const index = laneToIndex(updateLane); + // We can always overwrite an existing timestamp because we prefer the most + // recent event, and we assume time is monotonically increasing. + eventTimes[index] = eventTime; } export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) { @@ -723,13 +756,18 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) { root.entangledLanes &= remainingLanes; + const entanglements = root.entanglements; + const eventTimes = root.eventTimes; const expirationTimes = root.expirationTimes; + + // Clear the lanes that no longer have pending work let lanes = noLongerPendingLanes; while (lanes > 0) { const index = pickArbitraryLaneIndex(lanes); const lane = 1 << index; - // Clear the expiration time + entanglements[index] = NoLanes; + eventTimes[index] = NoTimestamp; expirationTimes[index] = NoTimestamp; lanes &= ~lane; diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js index 31603c68d41e4..eaa4aba574d81 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.new.js +++ b/packages/react-reconciler/src/ReactFiberRoot.new.js @@ -40,6 +40,7 @@ function FiberRootNode(containerInfo, tag, hydrate) { this.callbackNode = null; this.callbackId = NoLanes; this.callbackPriority = NoLanePriority; + this.eventTimes = createLaneMap(NoLanes); this.expirationTimes = createLaneMap(NoTimestamp); this.pendingLanes = NoLanes; diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index 02506c7d41096..276208db25834 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -40,6 +40,7 @@ function FiberRootNode(containerInfo, tag, hydrate) { this.callbackNode = null; this.callbackId = NoLanes; this.callbackPriority = NoLanePriority; + this.eventTimes = createLaneMap(NoLanes); this.expirationTimes = createLaneMap(NoTimestamp); this.pendingLanes = NoLanes; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 535100dff2d7f..bf6e813b2d2fe 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -171,6 +171,7 @@ import { getCurrentUpdateLanePriority, markStarvedLanesAsExpired, getLanesToRetrySynchronouslyOnError, + getMostRecentEventTime, markRootUpdated, markRootSuspended as markRootSuspended_dontCallThisOneDirectly, markRootPinged, @@ -294,8 +295,6 @@ const subtreeRenderLanesCursor: StackCursor = createCursor(NoLanes); let workInProgressRootExitStatus: RootExitStatus = RootIncomplete; // A fatal error, if one is thrown let workInProgressRootFatalError: mixed = null; -// Most recent event time among processed updates during this render. -let workInProgressRootLatestProcessedEventTime: number = NoTimestamp; let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp; let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null; // "Included" lanes refer to lanes that were worked on during this render. It's @@ -540,6 +539,35 @@ export function scheduleUpdateOnFiber( return null; } + // Mark that the root has a pending update. + markRootUpdated(root, lane, eventTime); + + if (root === workInProgressRoot) { + // Received an update to a tree that's in the middle of rendering. Mark + // that there was an interleaved update work on this root. Unless the + // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render + // phase update. In that case, we don't treat render phase updates as if + // they were interleaved, for backwards compat reasons. + if ( + deferRenderPhaseUpdateToNextBatch || + (executionContext & RenderContext) === NoContext + ) { + workInProgressRootUpdatedLanes = mergeLanes( + workInProgressRootUpdatedLanes, + lane, + ); + } + if (workInProgressRootExitStatus === RootSuspendedWithDelay) { + // The root already suspended with a delay, which means this render + // definitely won't finish. Since we have a new update, let's mark it as + // suspended now, right before marking the incoming update. This has the + // effect of interrupting the current render and switching to the update. + // TODO: Make sure this doesn't override pings that happen while we've + // already started rendering. + markRootSuspended(root, workInProgressRootRenderLanes); + } + } + // TODO: requestUpdateLanePriority also reads the priority. Pass the // priority as an argument to that function and this one. const priorityLevel = getCurrentPriorityLevel(); @@ -605,82 +633,47 @@ export function scheduleUpdateOnFiber( // e.g. retrying a Suspense boundary isn't an update, but it does schedule work // on a fiber. function markUpdateLaneFromFiberToRoot( - fiber: Fiber, + sourceFiber: Fiber, lane: Lane, ): FiberRoot | null { // Update the source fiber's lanes - fiber.lanes = mergeLanes(fiber.lanes, lane); - let alternate = fiber.alternate; + sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); + let alternate = sourceFiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } if (__DEV__) { if ( alternate === null && - (fiber.effectTag & (Placement | Hydrating)) !== NoEffect + (sourceFiber.effectTag & (Placement | Hydrating)) !== NoEffect ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } // Walk the parent path to the root and update the child expiration time. - let node = fiber.return; - let root = null; - if (node === null && fiber.tag === HostRoot) { - root = fiber.stateNode; - } else { - while (node !== null) { - alternate = node.alternate; + let node = sourceFiber; + let parent = sourceFiber.return; + while (parent !== null) { + parent.childLanes = mergeLanes(parent.childLanes, lane); + alternate = parent.alternate; + if (alternate !== null) { + alternate.childLanes = mergeLanes(alternate.childLanes, lane); + } else { if (__DEV__) { - if ( - alternate === null && - (node.effectTag & (Placement | Hydrating)) !== NoEffect - ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + if ((parent.effectTag & (Placement | Hydrating)) !== NoEffect) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } - node.childLanes = mergeLanes(node.childLanes, lane); - if (alternate !== null) { - alternate.childLanes = mergeLanes(alternate.childLanes, lane); - } - if (node.return === null && node.tag === HostRoot) { - root = node.stateNode; - break; - } - node = node.return; } + node = parent; + parent = parent.return; } - - if (root !== null) { - // Mark that the root has a pending update. - markRootUpdated(root, lane); - if (workInProgressRoot === root) { - // Received an update to a tree that's in the middle of rendering. Mark - // that there was an interleaved update work on this root. Unless the - // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render - // phase update. In that case, we don't treat render phase updates as if - // they were interleaved, for backwards compat reasons. - if ( - deferRenderPhaseUpdateToNextBatch || - (executionContext & RenderContext) === NoContext - ) { - workInProgressRootUpdatedLanes = mergeLanes( - workInProgressRootUpdatedLanes, - lane, - ); - } - if (workInProgressRootExitStatus === RootSuspendedWithDelay) { - // The root already suspended with a delay, which means this render - // definitely won't finish. Since we have a new update, let's mark it as - // suspended now, right before marking the incoming update. This has the - // effect of interrupting the current render and switching to the update. - // TODO: Make sure this doesn't override pings that happen while we've - // already started rendering. - markRootSuspended(root, workInProgressRootRenderLanes); - } - } + if (node.tag === HostRoot) { + const root: FiberRoot = node.stateNode; + return root; + } else { + return null; } - - return root; } // Use this function to schedule a task for a root. There's only one task per @@ -944,12 +937,13 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { break; } + const mostRecentEventTime = getMostRecentEventTime(root, lanes); let msUntilTimeout; if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) { // We have processed a suspense config whose expiration time we // can use as the timeout. msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now(); - } else if (workInProgressRootLatestProcessedEventTime === NoTimestamp) { + } else if (mostRecentEventTime === NoTimestamp) { // This should never normally happen because only new updates // cause delayed states, so we should have processed something. // However, this could also happen in an offscreen tree. @@ -957,7 +951,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } else { // If we didn't process a suspense config, compute a JND based on // the amount of time elapsed since the most recent event time. - const eventTimeMs = workInProgressRootLatestProcessedEventTime; + const eventTimeMs = mostRecentEventTime; const timeElapsedMs = now() - eventTimeMs; msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; } @@ -980,10 +974,11 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } case RootCompleted: { // The work completed. Ready to commit. + const mostRecentEventTime = getMostRecentEventTime(root, lanes); if ( // do not delay if we're inside an act() scope !shouldForceFlushFallbacksInDEV() && - workInProgressRootLatestProcessedEventTime !== NoTimestamp && + mostRecentEventTime !== NoTimestamp && workInProgressRootCanSuspendUsingConfig !== null ) { // If we have exceeded the minimum loading delay, which probably @@ -991,7 +986,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { // a bit longer to ensure that the spinner is shown for // enough time. const msUntilTimeout = computeMsUntilSuspenseLoadingDelay( - workInProgressRootLatestProcessedEventTime, + mostRecentEventTime, workInProgressRootCanSuspendUsingConfig, ); if (msUntilTimeout > 10) { @@ -1329,7 +1324,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) { workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes; workInProgressRootExitStatus = RootIncomplete; workInProgressRootFatalError = null; - workInProgressRootLatestProcessedEventTime = NoTimestamp; workInProgressRootLatestSuspenseTimeout = NoTimestamp; workInProgressRootCanSuspendUsingConfig = null; workInProgressRootSkippedLanes = NoLanes; @@ -1447,11 +1441,6 @@ export function markRenderEventTimeAndConfig( eventTime: number, suspenseConfig: null | SuspenseConfig, ): void { - // Track the most recent event time of all updates processed in this batch. - if (workInProgressRootLatestProcessedEventTime < eventTime) { - workInProgressRootLatestProcessedEventTime = eventTime; - } - // Track the largest/latest timeout deadline in this batch. // TODO: If there are two transitions in the same batch, shouldn't we // choose the smaller one? Maybe this is because when an intermediate @@ -2908,6 +2897,7 @@ function captureCommitPhaseErrorOnRoot( const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -2944,6 +2934,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -3016,6 +3007,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane); if (root !== null) { + markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, retryLane); } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 42811a73b1f56..ea8c7b1a37bda 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -163,6 +163,7 @@ import { getCurrentUpdateLanePriority, markStarvedLanesAsExpired, getLanesToRetrySynchronouslyOnError, + getMostRecentEventTime, markRootUpdated, markRootSuspended as markRootSuspended_dontCallThisOneDirectly, markRootPinged, @@ -286,8 +287,6 @@ const subtreeRenderLanesCursor: StackCursor = createCursor(NoLanes); let workInProgressRootExitStatus: RootExitStatus = RootIncomplete; // A fatal error, if one is thrown let workInProgressRootFatalError: mixed = null; -// Most recent event time among processed updates during this render. -let workInProgressRootLatestProcessedEventTime: number = NoTimestamp; let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp; let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null; // "Included" lanes refer to lanes that were worked on during this render. It's @@ -533,6 +532,35 @@ export function scheduleUpdateOnFiber( return null; } + // Mark that the root has a pending update. + markRootUpdated(root, lane, eventTime); + + if (root === workInProgressRoot) { + // Received an update to a tree that's in the middle of rendering. Mark + // that there was an interleaved update work on this root. Unless the + // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render + // phase update. In that case, we don't treat render phase updates as if + // they were interleaved, for backwards compat reasons. + if ( + deferRenderPhaseUpdateToNextBatch || + (executionContext & RenderContext) === NoContext + ) { + workInProgressRootUpdatedLanes = mergeLanes( + workInProgressRootUpdatedLanes, + lane, + ); + } + if (workInProgressRootExitStatus === RootSuspendedWithDelay) { + // The root already suspended with a delay, which means this render + // definitely won't finish. Since we have a new update, let's mark it as + // suspended now, right before marking the incoming update. This has the + // effect of interrupting the current render and switching to the update. + // TODO: Make sure this doesn't override pings that happen while we've + // already started rendering. + markRootSuspended(root, workInProgressRootRenderLanes); + } + } + // TODO: requestUpdateLanePriority also reads the priority. Pass the // priority as an argument to that function and this one. const priorityLevel = getCurrentPriorityLevel(); @@ -598,82 +626,47 @@ export function scheduleUpdateOnFiber( // e.g. retrying a Suspense boundary isn't an update, but it does schedule work // on a fiber. function markUpdateLaneFromFiberToRoot( - fiber: Fiber, + sourceFiber: Fiber, lane: Lane, ): FiberRoot | null { // Update the source fiber's lanes - fiber.lanes = mergeLanes(fiber.lanes, lane); - let alternate = fiber.alternate; + sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); + let alternate = sourceFiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } if (__DEV__) { if ( alternate === null && - (fiber.effectTag & (Placement | Hydrating)) !== NoEffect + (sourceFiber.effectTag & (Placement | Hydrating)) !== NoEffect ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } // Walk the parent path to the root and update the child expiration time. - let node = fiber.return; - let root = null; - if (node === null && fiber.tag === HostRoot) { - root = fiber.stateNode; - } else { - while (node !== null) { - alternate = node.alternate; + let node = sourceFiber; + let parent = sourceFiber.return; + while (parent !== null) { + parent.childLanes = mergeLanes(parent.childLanes, lane); + alternate = parent.alternate; + if (alternate !== null) { + alternate.childLanes = mergeLanes(alternate.childLanes, lane); + } else { if (__DEV__) { - if ( - alternate === null && - (node.effectTag & (Placement | Hydrating)) !== NoEffect - ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(fiber); + if ((parent.effectTag & (Placement | Hydrating)) !== NoEffect) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); } } - node.childLanes = mergeLanes(node.childLanes, lane); - if (alternate !== null) { - alternate.childLanes = mergeLanes(alternate.childLanes, lane); - } - if (node.return === null && node.tag === HostRoot) { - root = node.stateNode; - break; - } - node = node.return; } + node = parent; + parent = parent.return; } - - if (root !== null) { - // Mark that the root has a pending update. - markRootUpdated(root, lane); - if (workInProgressRoot === root) { - // Received an update to a tree that's in the middle of rendering. Mark - // that there was an interleaved update work on this root. Unless the - // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render - // phase update. In that case, we don't treat render phase updates as if - // they were interleaved, for backwards compat reasons. - if ( - deferRenderPhaseUpdateToNextBatch || - (executionContext & RenderContext) === NoContext - ) { - workInProgressRootUpdatedLanes = mergeLanes( - workInProgressRootUpdatedLanes, - lane, - ); - } - if (workInProgressRootExitStatus === RootSuspendedWithDelay) { - // The root already suspended with a delay, which means this render - // definitely won't finish. Since we have a new update, let's mark it as - // suspended now, right before marking the incoming update. This has the - // effect of interrupting the current render and switching to the update. - // TODO: Make sure this doesn't override pings that happen while we've - // already started rendering. - markRootSuspended(root, workInProgressRootRenderLanes); - } - } + if (node.tag === HostRoot) { + const root: FiberRoot = node.stateNode; + return root; + } else { + return null; } - - return root; } // Use this function to schedule a task for a root. There's only one task per @@ -937,12 +930,13 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { break; } + const mostRecentEventTime = getMostRecentEventTime(root, lanes); let msUntilTimeout; if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) { // We have processed a suspense config whose expiration time we // can use as the timeout. msUntilTimeout = workInProgressRootLatestSuspenseTimeout - now(); - } else if (workInProgressRootLatestProcessedEventTime === NoTimestamp) { + } else if (mostRecentEventTime === NoTimestamp) { // This should never normally happen because only new updates // cause delayed states, so we should have processed something. // However, this could also happen in an offscreen tree. @@ -950,7 +944,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } else { // If we didn't process a suspense config, compute a JND based on // the amount of time elapsed since the most recent event time. - const eventTimeMs = workInProgressRootLatestProcessedEventTime; + const eventTimeMs = mostRecentEventTime; const timeElapsedMs = now() - eventTimeMs; msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; } @@ -973,10 +967,11 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { } case RootCompleted: { // The work completed. Ready to commit. + const mostRecentEventTime = getMostRecentEventTime(root, lanes); if ( // do not delay if we're inside an act() scope !shouldForceFlushFallbacksInDEV() && - workInProgressRootLatestProcessedEventTime !== NoTimestamp && + mostRecentEventTime !== NoTimestamp && workInProgressRootCanSuspendUsingConfig !== null ) { // If we have exceeded the minimum loading delay, which probably @@ -984,7 +979,7 @@ function finishConcurrentRender(root, finishedWork, exitStatus, lanes) { // a bit longer to ensure that the spinner is shown for // enough time. const msUntilTimeout = computeMsUntilSuspenseLoadingDelay( - workInProgressRootLatestProcessedEventTime, + mostRecentEventTime, workInProgressRootCanSuspendUsingConfig, ); if (msUntilTimeout > 10) { @@ -1322,7 +1317,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) { workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes; workInProgressRootExitStatus = RootIncomplete; workInProgressRootFatalError = null; - workInProgressRootLatestProcessedEventTime = NoTimestamp; workInProgressRootLatestSuspenseTimeout = NoTimestamp; workInProgressRootCanSuspendUsingConfig = null; workInProgressRootSkippedLanes = NoLanes; @@ -1440,11 +1434,6 @@ export function markRenderEventTimeAndConfig( eventTime: number, suspenseConfig: null | SuspenseConfig, ): void { - // Track the most recent event time of all updates processed in this batch. - if (workInProgressRootLatestProcessedEventTime < eventTime) { - workInProgressRootLatestProcessedEventTime = eventTime; - } - // Track the largest/latest timeout deadline in this batch. // TODO: If there are two transitions in the same batch, shouldn't we // choose the smaller one? Maybe this is because when an intermediate @@ -2764,6 +2753,7 @@ function captureCommitPhaseErrorOnRoot( const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -2800,6 +2790,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane)); if (root !== null) { + markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, SyncLane); } @@ -2872,6 +2863,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { const eventTime = requestEventTime(); const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane); if (root !== null) { + markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, retryLane); } diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 1a638d42d5a0f..51b8f9ec188bb 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -217,6 +217,7 @@ type BaseFiberRootProperties = {| // if it's already working. callbackId: Lanes, callbackPriority: LanePriority, + eventTimes: LaneMap, expirationTimes: LaneMap, pendingLanes: Lanes,