From a1d4c827f36937034edea8e2eada48753886021f Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 30 Sep 2025 16:23:43 -0400 Subject: [PATCH 1/3] Reset EventTime when clearing timers We need to track repeat updates separately. --- .../src/ReactFiberWorkLoop.js | 12 +++--- .../src/ReactProfilerTimer.js | 42 ++++++++++--------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index cd193d04e45fa..3de2ab6b16f9f 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -281,7 +281,7 @@ import { blockingUpdateComponentName, blockingEventTime, blockingEventType, - blockingEventIsRepeat, + blockingEventRepeatTime, blockingSuspendedTime, gestureClampTime, gestureUpdateTime, @@ -291,7 +291,7 @@ import { gestureUpdateComponentName, gestureEventTime, gestureEventType, - gestureEventIsRepeat, + gestureEventRepeatTime, gestureSuspendedTime, transitionClampTime, transitionStartTime, @@ -302,7 +302,7 @@ import { transitionUpdateComponentName, transitionEventTime, transitionEventType, - transitionEventIsRepeat, + transitionEventRepeatTime, transitionSuspendedTime, clearBlockingTimers, clearGestureTimers, @@ -2017,7 +2017,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { clampedUpdateTime, clampedEventTime, gestureEventType, - gestureEventIsRepeat, + gestureEventRepeatTime > 0, gestureUpdateType === PINGED_UPDATE, renderStartTime, gestureUpdateTask, @@ -2065,7 +2065,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { clampedUpdateTime, clampedEventTime, blockingEventType, - blockingEventIsRepeat, + blockingEventRepeatTime > 0, blockingUpdateType === SPAWNED_UPDATE, blockingUpdateType === PINGED_UPDATE, renderStartTime, @@ -2119,7 +2119,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { clampedUpdateTime, clampedEventTime, transitionEventType, - transitionEventIsRepeat, + transitionEventRepeatTime > 0, transitionUpdateType === PINGED_UPDATE, renderStartTime, transitionUpdateTask, diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 152810f85068c..1818f4f83842b 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -73,7 +73,7 @@ export let blockingUpdateMethodName: null | string = null; // The name of the me export let blockingUpdateComponentName: null | string = null; // The name of the component where first sync update happened. export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState. export let blockingEventType: null | string = null; // Event type of the first setState. -export let blockingEventIsRepeat: boolean = false; +export let blockingEventRepeatTime: number = -1.1; export let blockingSuspendedTime: number = -1.1; export let gestureClampTime: number = -0; @@ -84,7 +84,7 @@ export let gestureUpdateMethodName: null | string = null; // The name of the met export let gestureUpdateComponentName: null | string = null; // The name of the component where first gesture update happened. export let gestureEventTime: number = -1.1; // Event timeStamp of the first setState. export let gestureEventType: null | string = null; // Event type of the first setState. -export let gestureEventIsRepeat: boolean = false; +export let gestureEventRepeatTime: number = -1.1; export let gestureSuspendedTime: number = -1.1; // TODO: This should really be one per Transition lane. @@ -97,7 +97,7 @@ export let transitionUpdateMethodName: null | string = null; // The name of the export let transitionUpdateComponentName: null | string = null; // The name of the component where first transition update happened. export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition. export let transitionEventType: null | string = null; // Event type of the first transition. -export let transitionEventIsRepeat: boolean = false; +export let transitionEventRepeatTime: number = -1.1; export let transitionSuspendedTime: number = -1.1; export let retryClampTime: number = -0; @@ -136,10 +136,10 @@ export function startUpdateTimerByLane( const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); if ( - newEventTime !== gestureEventTime || + newEventTime !== gestureEventRepeatTime || newEventType !== gestureEventType ) { - gestureEventIsRepeat = false; + gestureEventRepeatTime = -1.1; } gestureEventTime = newEventTime; gestureEventType = newEventType; @@ -158,10 +158,10 @@ export function startUpdateTimerByLane( const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); if ( - newEventTime !== blockingEventTime || + newEventTime !== blockingEventRepeatTime || newEventType !== blockingEventType ) { - blockingEventIsRepeat = false; + gestureEventRepeatTime = -1.1; } else if (newEventType !== null) { // If this is a second update in the same event, we treat it as a spawned update. // This might be a microtask spawned from useEffect, multiple flushSync or @@ -183,10 +183,10 @@ export function startUpdateTimerByLane( const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); if ( - newEventTime !== transitionEventTime || + newEventTime !== transitionEventRepeatTime || newEventType !== transitionEventType ) { - transitionEventIsRepeat = false; + transitionEventRepeatTime = -1.1; } transitionEventTime = newEventTime; transitionEventType = newEventType; @@ -211,10 +211,10 @@ export function startHostActionTimer(fiber: Fiber): void { const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); if ( - newEventTime !== blockingEventTime || + newEventTime !== blockingEventRepeatTime || newEventType !== blockingEventType ) { - blockingEventIsRepeat = false; + blockingEventRepeatTime = -1.1; } else if (newEventType !== null) { // If this is a second update in the same event, we treat it as a spawned update. // This might be a microtask spawned from useEffect, multiple flushSync or @@ -232,10 +232,10 @@ export function startHostActionTimer(fiber: Fiber): void { const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); if ( - newEventTime !== transitionEventTime || + newEventTime !== transitionEventRepeatTime || newEventType !== transitionEventType ) { - transitionEventIsRepeat = false; + transitionEventRepeatTime = -1.1; } transitionEventTime = newEventTime; transitionEventType = newEventType; @@ -290,7 +290,8 @@ export function clearBlockingTimers(): void { blockingUpdateMethodName = null; blockingUpdateComponentName = null; blockingSuspendedTime = -1.1; - blockingEventIsRepeat = true; + blockingEventRepeatTime = blockingEventTime; + blockingEventTime = -1.1; blockingClampTime = now(); } @@ -303,10 +304,10 @@ export function startAsyncTransitionTimer(): void { const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); if ( - newEventTime !== transitionEventTime || + newEventTime !== transitionEventRepeatTime || newEventType !== transitionEventType ) { - transitionEventIsRepeat = false; + transitionEventRepeatTime = -1.1; } transitionEventTime = newEventTime; transitionEventType = newEventType; @@ -327,7 +328,8 @@ export function clearTransitionTimers(): void { transitionUpdateTime = -1.1; transitionUpdateType = 0; transitionSuspendedTime = -1.1; - transitionEventIsRepeat = true; + transitionEventRepeatTime = transitionEventTime; + transitionEventTime = -1.1; transitionClampTime = now(); } @@ -340,7 +342,8 @@ export function clearGestureTimers(): void { gestureUpdateTime = -1.1; gestureUpdateType = 0; gestureSuspendedTime = -1.1; - gestureEventIsRepeat = true; + gestureEventRepeatTime = gestureEventTime; + gestureEventTime = -1.1; gestureClampTime = now(); } @@ -350,7 +353,8 @@ export function clearGestureUpdates(): void { gestureUpdateTime = -1.1; gestureUpdateType = 0; gestureSuspendedTime = -1.1; - gestureEventIsRepeat = true; + gestureEventRepeatTime = gestureEventTime; + gestureEventTime = -1.1; } export function clampBlockingTimers(finalTime: number): void { From f9dfbe1ca2d91854e0f8ddb7de8c3f6101c32223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 30 Sep 2025 17:16:50 -0400 Subject: [PATCH 2/3] copypasta Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> --- packages/react-reconciler/src/ReactProfilerTimer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 1818f4f83842b..a83c781a80916 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -161,7 +161,7 @@ export function startUpdateTimerByLane( newEventTime !== blockingEventRepeatTime || newEventType !== blockingEventType ) { - gestureEventRepeatTime = -1.1; + blockingEventRepeatTime = -1.1; } else if (newEventType !== null) { // If this is a second update in the same event, we treat it as a spawned update. // This might be a microtask spawned from useEffect, multiple flushSync or From a7f364d32208cd1660dcabed88016ae61de2390a Mon Sep 17 00:00:00 2001 From: Ricky Date: Wed, 1 Oct 2025 08:05:20 -0400 Subject: [PATCH 3/3] Update ReactProfilerTimer.js Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> --- packages/react-reconciler/src/ReactProfilerTimer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index a83c781a80916..512ecceca9e41 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -161,7 +161,7 @@ export function startUpdateTimerByLane( newEventTime !== blockingEventRepeatTime || newEventType !== blockingEventType ) { - blockingEventRepeatTime = -1.1; + blockingEventRepeatTime = -1.1; } else if (newEventType !== null) { // If this is a second update in the same event, we treat it as a spawned update. // This might be a microtask spawned from useEffect, multiple flushSync or