Fix "Consecutive" Event Logs in Performance Track (#34659)
Reset EventTime when clearing timers. We need to track repeat updates separately. Typically we always reset all timers when we've logged an update. The same update shouldn't be logged again. I was trying to be clever and not reset the XEventTime because we also need the timestamp to know if it's a repeat event. However, because of this it looked like we had an event schedule an update even after we had reset them. This always resets the XEventTime to -1.1 and then stashes the old time on EventRepeatTime which is our indication whether the next update was a repeat of the old event. --------- Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> Co-authored-by: Ricky <rickhanlonii@gmail.com>
Sebastian Markbåge committed
Oct 1, 2025 at 10:53 UTC
7bccdbd765f03254658da9086e9c5763842aa3ed
2 files changed
+29
-25
packages/react-reconciler/src/ReactFiberWorkLoop.js
+6
-6
@@ -281,7 +281,7 @@ import {
281
blockingUpdateComponentName,
282
blockingEventTime,
283
blockingEventType,
284
- blockingEventIsRepeat,
284
+ blockingEventRepeatTime,
285
blockingSuspendedTime,
286
gestureClampTime,
287
gestureUpdateTime,
@@ -291,7 +291,7 @@ import {
291
gestureUpdateComponentName,
292
gestureEventTime,
293
gestureEventType,
294
- gestureEventIsRepeat,
294
+ gestureEventRepeatTime,
295
gestureSuspendedTime,
296
transitionClampTime,
297
transitionStartTime,
@@ -302,7 +302,7 @@ import {
302
transitionUpdateComponentName,
303
transitionEventTime,
304
transitionEventType,
305
- transitionEventIsRepeat,
305
+ transitionEventRepeatTime,
306
transitionSuspendedTime,
307
clearBlockingTimers,
308
clearGestureTimers,
@@ -2017,7 +2017,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
2017
clampedUpdateTime,
2018
clampedEventTime,
2019
gestureEventType,
2020
- gestureEventIsRepeat,
2020
+ gestureEventRepeatTime > 0,
2021
gestureUpdateType === PINGED_UPDATE,
2022
renderStartTime,
2023
gestureUpdateTask,
@@ -2065,7 +2065,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
2065
clampedUpdateTime,
2066
clampedEventTime,
2067
blockingEventType,
2068
- blockingEventIsRepeat,
2068
+ blockingEventRepeatTime > 0,
2069
blockingUpdateType === SPAWNED_UPDATE,
2070
blockingUpdateType === PINGED_UPDATE,
2071
renderStartTime,
@@ -2119,7 +2119,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
2119
clampedUpdateTime,
2120
clampedEventTime,
2121
transitionEventType,
2122
- transitionEventIsRepeat,
2122
+ transitionEventRepeatTime > 0,
2123
transitionUpdateType === PINGED_UPDATE,
2124
renderStartTime,
2125
transitionUpdateTask,
packages/react-reconciler/src/ReactProfilerTimer.js
+23
-19
@@ -74,7 +74,7 @@ export let blockingUpdateMethodName: null | string = null; // The name of the me
74
export let blockingUpdateComponentName: null | string = null; // The name of the component where first sync update happened.
75
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
76
export let blockingEventType: null | string = null; // Event type of the first setState.
77
-export let blockingEventIsRepeat: boolean = false;
77
+export let blockingEventRepeatTime: number = -1.1;
78
export let blockingSuspendedTime: number = -1.1;
79
80
export let gestureClampTime: number = -0;
@@ -85,7 +85,7 @@ export let gestureUpdateMethodName: null | string = null; // The name of the met
85
export let gestureUpdateComponentName: null | string = null; // The name of the component where first gesture update happened.
86
export let gestureEventTime: number = -1.1; // Event timeStamp of the first setState.
87
export let gestureEventType: null | string = null; // Event type of the first setState.
88
-export let gestureEventIsRepeat: boolean = false;
88
+export let gestureEventRepeatTime: number = -1.1;
89
export let gestureSuspendedTime: number = -1.1;
90
91
// TODO: This should really be one per Transition lane.
@@ -98,7 +98,7 @@ export let transitionUpdateMethodName: null | string = null; // The name of the
98
export let transitionUpdateComponentName: null | string = null; // The name of the component where first transition update happened.
99
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
100
export let transitionEventType: null | string = null; // Event type of the first transition.
101
-export let transitionEventIsRepeat: boolean = false;
101
+export let transitionEventRepeatTime: number = -1.1;
102
export let transitionSuspendedTime: number = -1.1;
103
104
export let retryClampTime: number = -0;
@@ -137,10 +137,10 @@ export function startUpdateTimerByLane(
137
const newEventTime = resolveEventTimeStamp();
138
const newEventType = resolveEventType();
139
if (
140
- newEventTime !== gestureEventTime ||
140
+ newEventTime !== gestureEventRepeatTime ||
141
newEventType !== gestureEventType
142
) {
143
- gestureEventIsRepeat = false;
143
+ gestureEventRepeatTime = -1.1;
144
}
145
gestureEventTime = newEventTime;
146
gestureEventType = newEventType;
@@ -160,10 +160,10 @@ export function startUpdateTimerByLane(
160
const newEventTime = resolveEventTimeStamp();
161
const newEventType = resolveEventType();
162
if (
163
- newEventTime !== blockingEventTime ||
163
+ newEventTime !== blockingEventRepeatTime ||
164
newEventType !== blockingEventType
165
) {
166
- blockingEventIsRepeat = false;
166
+ blockingEventRepeatTime = -1.1;
167
} else if (newEventType !== null) {
168
// If this is a second update in the same event, we treat it as a spawned update.
169
// This might be a microtask spawned from useEffect, multiple flushSync or
@@ -185,10 +185,10 @@ export function startUpdateTimerByLane(
185
const newEventTime = resolveEventTimeStamp();
186
const newEventType = resolveEventType();
187
if (
188
- newEventTime !== transitionEventTime ||
188
+ newEventTime !== transitionEventRepeatTime ||
189
newEventType !== transitionEventType
190
) {
191
- transitionEventIsRepeat = false;
191
+ transitionEventRepeatTime = -1.1;
192
}
193
transitionEventTime = newEventTime;
194
transitionEventType = newEventType;
@@ -213,10 +213,10 @@ export function startHostActionTimer(fiber: Fiber): void {
213
const newEventTime = resolveEventTimeStamp();
214
const newEventType = resolveEventType();
215
if (
216
- newEventTime !== blockingEventTime ||
216
+ newEventTime !== blockingEventRepeatTime ||
217
newEventType !== blockingEventType
218
) {
219
- blockingEventIsRepeat = false;
219
+ blockingEventRepeatTime = -1.1;
220
} else if (newEventType !== null) {
221
// If this is a second update in the same event, we treat it as a spawned update.
222
// This might be a microtask spawned from useEffect, multiple flushSync or
@@ -234,10 +234,10 @@ export function startHostActionTimer(fiber: Fiber): void {
234
const newEventTime = resolveEventTimeStamp();
235
const newEventType = resolveEventType();
236
if (
237
- newEventTime !== transitionEventTime ||
237
+ newEventTime !== transitionEventRepeatTime ||
238
newEventType !== transitionEventType
239
) {
240
- transitionEventIsRepeat = false;
240
+ transitionEventRepeatTime = -1.1;
241
}
242
transitionEventTime = newEventTime;
243
transitionEventType = newEventType;
@@ -292,7 +292,8 @@ export function clearBlockingTimers(): void {
292
blockingUpdateMethodName = null;
293
blockingUpdateComponentName = null;
294
blockingSuspendedTime = -1.1;
295
- blockingEventIsRepeat = true;
295
+ blockingEventRepeatTime = blockingEventTime;
296
+ blockingEventTime = -1.1;
297
blockingClampTime = now();
298
}
299
@@ -305,10 +306,10 @@ export function startAsyncTransitionTimer(): void {
306
const newEventTime = resolveEventTimeStamp();
307
const newEventType = resolveEventType();
308
if (
308
- newEventTime !== transitionEventTime ||
309
+ newEventTime !== transitionEventRepeatTime ||
310
newEventType !== transitionEventType
311
) {
311
- transitionEventIsRepeat = false;
312
+ transitionEventRepeatTime = -1.1;
313
}
314
transitionEventTime = newEventTime;
315
transitionEventType = newEventType;
@@ -329,7 +330,8 @@ export function clearTransitionTimers(): void {
330
transitionUpdateTime = -1.1;
331
transitionUpdateType = 0;
332
transitionSuspendedTime = -1.1;
332
- transitionEventIsRepeat = true;
333
+ transitionEventRepeatTime = transitionEventTime;
334
+ transitionEventTime = -1.1;
335
transitionClampTime = now();
336
}
337
@@ -342,7 +344,8 @@ export function clearGestureTimers(): void {
344
gestureUpdateTime = -1.1;
345
gestureUpdateType = 0;
346
gestureSuspendedTime = -1.1;
345
- gestureEventIsRepeat = true;
347
+ gestureEventRepeatTime = gestureEventTime;
348
+ gestureEventTime = -1.1;
349
gestureClampTime = now();
350
}
351
@@ -352,7 +355,8 @@ export function clearGestureUpdates(): void {
355
gestureUpdateTime = -1.1;
356
gestureUpdateType = 0;
357
gestureSuspendedTime = -1.1;
355
- gestureEventIsRepeat = true;
358
+ gestureEventRepeatTime = gestureEventTime;
359
+ gestureEventTime = -1.1;
360
}
361
362
export function clampBlockingTimers(finalTime: number): void {