@samitouri / QOS-React-2 / commits / 62960c67c8

Run Component Track Logs in the console.createTask() of the Fiber (#32809)

Stacked on #32736. That way you can find the owner stack of each component that rerendered for context. In addition to the JSX callsite tasks that we already track, I also added tracking of the first `setState` call before rendering. We then run the "Update" entries in that task. That way you can find the callsite of the first setState and therefore the "cause" of a render starting by selecting the "Update" track. Unfortunately this is blocked on bugs in Chrome that makes it so that these stacks are not reliable in the Performance tab. It basically just doesn't work.

Sebastian Markbåge committed Apr 29, 2025 at 22:17 UTC 62960c67c84da7e85d7515ef8447cea0ba0824a6
7 files changed +358 -113
packages/react-client/src/ReactFlightPerformanceTrack.js
+48 -16
@@ -78,14 +78,30 @@ export function logComponentRender(
78 : 'error';
79 const entryName =
80 isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
81 - console.timeStamp(
82 - entryName,
83 - startTime < 0 ? 0 : startTime,
84 - childrenEndTime,
85 - trackNames[trackIdx],
86 - COMPONENTS_TRACK,
87 - color,
88 - );
81 + const debugTask = componentInfo.debugTask;
82 + if (__DEV__ && debugTask) {
83 + debugTask.run(
84 + // $FlowFixMe[method-unbinding]
85 + console.timeStamp.bind(
86 + console,
87 + entryName,
88 + startTime < 0 ? 0 : startTime,
89 + childrenEndTime,
90 + trackNames[trackIdx],
91 + COMPONENTS_TRACK,
92 + color,
93 + ),
94 + );
95 + } else {
96 + console.timeStamp(
97 + entryName,
98 + startTime < 0 ? 0 : startTime,
99 + childrenEndTime,
100 + trackNames[trackIdx],
101 + COMPONENTS_TRACK,
102 + color,
103 + );
104 + }
105 }
106 }
107
@@ -154,13 +170,29 @@ export function logDedupedComponentRender(
170 if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
171 const name = componentInfo.name;
172 const entryName = name + ' [deduped]';
157 - console.timeStamp(
158 - entryName,
159 - startTime < 0 ? 0 : startTime,
160 - endTime,
161 - trackNames[trackIdx],
162 - COMPONENTS_TRACK,
163 - 'tertiary-light',
164 - );
173 + const debugTask = componentInfo.debugTask;
174 + if (__DEV__ && debugTask) {
175 + debugTask.run(
176 + // $FlowFixMe[method-unbinding]
177 + console.timeStamp.bind(
178 + console,
179 + entryName,
180 + startTime < 0 ? 0 : startTime,
181 + endTime,
182 + trackNames[trackIdx],
183 + COMPONENTS_TRACK,
184 + 'tertiary-light',
185 + ),
186 + );
187 + } else {
188 + console.timeStamp(
189 + entryName,
190 + startTime < 0 ? 0 : startTime,
191 + endTime,
192 + trackNames[trackIdx],
193 + COMPONENTS_TRACK,
194 + 'tertiary-light',
195 + );
196 + }
197 }
198 }
packages/react-reconciler/src/ReactFiberClassComponent.js
+3 -3
@@ -180,7 +180,7 @@ const classComponentUpdater = {
180
181 const root = enqueueUpdate(fiber, update, lane);
182 if (root !== null) {
183 - startUpdateTimerByLane(lane);
183 + startUpdateTimerByLane(lane, 'this.setState()');
184 scheduleUpdateOnFiber(root, fiber, lane);
185 entangleTransitions(root, fiber, lane);
186 }
@@ -206,7 +206,7 @@ const classComponentUpdater = {
206
207 const root = enqueueUpdate(fiber, update, lane);
208 if (root !== null) {
209 - startUpdateTimerByLane(lane);
209 + startUpdateTimerByLane(lane, 'this.replaceState()');
210 scheduleUpdateOnFiber(root, fiber, lane);
211 entangleTransitions(root, fiber, lane);
212 }
@@ -232,7 +232,7 @@ const classComponentUpdater = {
232
233 const root = enqueueUpdate(fiber, update, lane);
234 if (root !== null) {
235 - startUpdateTimerByLane(lane);
235 + startUpdateTimerByLane(lane, 'this.forceUpdate()');
236 scheduleUpdateOnFiber(root, fiber, lane);
237 entangleTransitions(root, fiber, lane);
238 }
packages/react-reconciler/src/ReactFiberHooks.js
+7 -4
@@ -1847,6 +1847,8 @@ function updateStoreInstance<T>(
1847 // snapsho and getSnapshot values to bail out. We need to check one more time.
1848 if (checkIfSnapshotChanged(inst)) {
1849 // Force a re-render.
1850 + // We intentionally don't log update times and stacks here because this
1851 + // was not an external trigger but rather an internal one.
1852 forceStoreRerender(fiber);
1853 }
1854 }
@@ -1861,6 +1863,7 @@ function subscribeToStore<T>(
1863 // read from the store.
1864 if (checkIfSnapshotChanged(inst)) {
1865 // Force a re-render.
1866 + startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()');
1867 forceStoreRerender(fiber);
1868 }
1869 };
@@ -3503,7 +3506,7 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T): void {
3506 const refreshUpdate = createLegacyQueueUpdate(lane);
3507 const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
3508 if (root !== null) {
3506 - startUpdateTimerByLane(lane);
3509 + startUpdateTimerByLane(lane, 'refresh()');
3510 scheduleUpdateOnFiber(root, provider, lane);
3511 entangleLegacyQueueTransitions(root, provider, lane);
3512 }
@@ -3572,7 +3575,7 @@ function dispatchReducerAction<S, A>(
3575 } else {
3576 const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
3577 if (root !== null) {
3575 - startUpdateTimerByLane(lane);
3578 + startUpdateTimerByLane(lane, 'dispatch()');
3579 scheduleUpdateOnFiber(root, fiber, lane);
3580 entangleTransitionUpdate(root, queue, lane);
3581 }
@@ -3606,7 +3609,7 @@ function dispatchSetState<S, A>(
3609 lane,
3610 );
3611 if (didScheduleUpdate) {
3609 - startUpdateTimerByLane(lane);
3612 + startUpdateTimerByLane(lane, 'setState()');
3613 }
3614 markUpdateInDevTools(fiber, lane, action);
3615 }
@@ -3768,7 +3771,7 @@ function dispatchOptimisticSetState<S, A>(
3771 // will never be attempted before the optimistic update. This currently
3772 // holds because the optimistic update is always synchronous. If we ever
3773 // change that, we'll need to account for this.
3771 - startUpdateTimerByLane(lane);
3774 + startUpdateTimerByLane(lane, 'setOptimistic()');
3775 scheduleUpdateOnFiber(root, fiber, lane);
3776 // Optimistic updates are always synchronous, so we don't need to call
3777 // entangleTransitionUpdate here.
packages/react-reconciler/src/ReactFiberPerformanceTrack.js
+280 -88
@@ -103,14 +103,29 @@ function logComponentTrigger(
103 trigger: string,
104 ) {
105 if (supportsUserTiming) {
106 - console.timeStamp(
107 - trigger,
108 - startTime,
109 - endTime,
110 - COMPONENTS_TRACK,
111 - undefined,
112 - 'warning',
113 - );
106 + const debugTask = fiber._debugTask;
107 + if (__DEV__ && debugTask) {
108 + debugTask.run(
109 + console.timeStamp.bind(
110 + console,
111 + trigger,
112 + startTime,
113 + endTime,
114 + COMPONENTS_TRACK,
115 + undefined,
116 + 'warning',
117 + ),
118 + );
119 + } else {
120 + console.timeStamp(
121 + trigger,
122 + startTime,
123 + endTime,
124 + COMPONENTS_TRACK,
125 + undefined,
126 + 'warning',
127 + );
128 + }
129 }
130 }
131
@@ -178,14 +193,30 @@ export function logComponentRender(
193 ? 'tertiary-dark'
194 : 'primary-dark'
195 : 'error';
181 - console.timeStamp(
182 - name,
183 - startTime,
184 - endTime,
185 - COMPONENTS_TRACK,
186 - undefined,
187 - color,
188 - );
196 + const debugTask = fiber._debugTask;
197 + if (__DEV__ && debugTask) {
198 + debugTask.run(
199 + // $FlowFixMe[method-unbinding]
200 + console.timeStamp.bind(
201 + console,
202 + name,
203 + startTime,
204 + endTime,
205 + COMPONENTS_TRACK,
206 + undefined,
207 + color,
208 + ),
209 + );
210 + } else {
211 + console.timeStamp(
212 + name,
213 + startTime,
214 + endTime,
215 + COMPONENTS_TRACK,
216 + undefined,
217 + color,
218 + );
219 + }
220 }
221 }
222
@@ -207,9 +238,18 @@ export function logComponentErrored(
238 // $FlowFixMe[method-unbinding]
239 typeof performance.measure === 'function'
240 ) {
241 + let debugTask: ?ConsoleTask = null;
242 const properties = [];
243 for (let i = 0; i < errors.length; i++) {
244 const capturedValue = errors[i];
245 + if (debugTask == null && capturedValue.source !== null) {
246 + // If the captured value has a source Fiber, use its debugTask for
247 + // the stack instead of the error boundary's stack. So you can find
248 + // which component errored since we don't show the errored render tree.
249 + // TODO: Ideally we should instead, store the failed fibers and log the
250 + // whole subtree including the component that errored.
251 + debugTask = capturedValue.source._debugTask;
252 + }
253 const error = capturedValue.value;
254 const message =
255 typeof error === 'object' &&
@@ -221,7 +261,12 @@ export function logComponentErrored(
261 String(error);
262 properties.push(['Error', message]);
263 }
224 - performance.measure(name, {
264 + if (debugTask == null) {
265 + // If the captured values don't have a debug task, fallback to the
266 + // error boundary itself.
267 + debugTask = fiber._debugTask;
268 + }
269 + const options = {
270 start: startTime,
271 end: endTime,
272 detail: {
@@ -235,7 +280,15 @@ export function logComponentErrored(
280 properties,
281 },
282 },
238 - });
283 + };
284 + if (__DEV__ && debugTask) {
285 + debugTask.run(
286 + // $FlowFixMe[method-unbinding]
287 + performance.measure.bind(performance, name, options),
288 + );
289 + } else {
290 + performance.measure(name, options);
291 + }
292 } else {
293 console.timeStamp(
294 name,
@@ -281,7 +334,7 @@ function logComponentEffectErrored(
334 String(error);
335 properties.push(['Error', message]);
336 }
284 - performance.measure(name, {
337 + const options = {
338 start: startTime,
339 end: endTime,
340 detail: {
@@ -292,7 +345,16 @@ function logComponentEffectErrored(
345 properties,
346 },
347 },
295 - });
348 + };
349 + const debugTask = fiber._debugTask;
350 + if (debugTask) {
351 + debugTask.run(
352 + // $FlowFixMe[method-unbinding]
353 + performance.measure.bind(performance, name, options),
354 + );
355 + } else {
356 + performance.measure(name, options);
357 + }
358 } else {
359 console.timeStamp(
360 name,
@@ -331,14 +393,30 @@ export function logComponentEffect(
393 : selfTime < 500
394 ? 'secondary-dark'
395 : 'error';
334 - console.timeStamp(
335 - name,
336 - startTime,
337 - endTime,
338 - COMPONENTS_TRACK,
339 - undefined,
340 - color,
341 - );
396 + const debugTask = fiber._debugTask;
397 + if (__DEV__ && debugTask) {
398 + debugTask.run(
399 + // $FlowFixMe[method-unbinding]
400 + console.timeStamp.bind(
401 + console,
402 + name,
403 + startTime,
404 + endTime,
405 + COMPONENTS_TRACK,
406 + undefined,
407 + color,
408 + ),
409 + );
410 + } else {
411 + console.timeStamp(
412 + name,
413 + startTime,
414 + endTime,
415 + COMPONENTS_TRACK,
416 + undefined,
417 + color,
418 + );
419 + }
420 }
421 }
422
@@ -379,14 +457,30 @@ export function logSuspendedYieldTime(
457 suspendedFiber: Fiber,
458 ): void {
459 if (supportsUserTiming) {
382 - console.timeStamp(
383 - 'Suspended',
384 - startTime,
385 - endTime,
386 - COMPONENTS_TRACK,
387 - undefined,
388 - 'primary-light',
389 - );
460 + const debugTask = suspendedFiber._debugTask;
461 + if (__DEV__ && debugTask) {
462 + debugTask.run(
463 + // $FlowFixMe[method-unbinding]
464 + console.timeStamp.bind(
465 + console,
466 + 'Suspended',
467 + startTime,
468 + endTime,
469 + COMPONENTS_TRACK,
470 + undefined,
471 + 'primary-light',
472 + ),
473 + );
474 + } else {
475 + console.timeStamp(
476 + 'Suspended',
477 + startTime,
478 + endTime,
479 + COMPONENTS_TRACK,
480 + undefined,
481 + 'primary-light',
482 + );
483 + }
484 }
485 }
486
@@ -396,14 +490,30 @@ export function logActionYieldTime(
490 suspendedFiber: Fiber,
491 ): void {
492 if (supportsUserTiming) {
399 - console.timeStamp(
400 - 'Action',
401 - startTime,
402 - endTime,
403 - COMPONENTS_TRACK,
404 - undefined,
405 - 'primary-light',
406 - );
493 + const debugTask = suspendedFiber._debugTask;
494 + if (__DEV__ && debugTask) {
495 + debugTask.run(
496 + // $FlowFixMe[method-unbinding]
497 + console.timeStamp.bind(
498 + console,
499 + 'Action',
500 + startTime,
501 + endTime,
502 + COMPONENTS_TRACK,
503 + undefined,
504 + 'primary-light',
505 + ),
506 + );
507 + } else {
508 + console.timeStamp(
509 + 'Action',
510 + startTime,
511 + endTime,
512 + COMPONENTS_TRACK,
513 + undefined,
514 + 'primary-light',
515 + );
516 + }
517 }
518 }
519
@@ -415,6 +525,7 @@ export function logBlockingStart(
525 isSpawnedUpdate: boolean,
526 renderStartTime: number,
527 lanes: Lanes,
528 + debugTask: null | ConsoleTask, // DEV-only
529 ): void {
530 if (supportsUserTiming) {
531 currentTrack = 'Blocking';
@@ -424,14 +535,29 @@ export function logBlockingStart(
535 if (eventTime > 0 && eventType !== null) {
536 // Log the time from the event timeStamp until we called setState.
537 const color = eventIsRepeat ? 'secondary-light' : 'warning';
427 - console.timeStamp(
428 - eventIsRepeat ? '' : 'Event: ' + eventType,
429 - eventTime,
430 - updateTime > 0 ? updateTime : renderStartTime,
431 - currentTrack,
432 - LANES_TRACK_GROUP,
433 - color,
434 - );
538 + if (__DEV__ && debugTask) {
539 + debugTask.run(
540 + // $FlowFixMe[method-unbinding]
541 + console.timeStamp.bind(
542 + console,
543 + eventIsRepeat ? '' : 'Event: ' + eventType,
544 + eventTime,
545 + updateTime > 0 ? updateTime : renderStartTime,
546 + currentTrack,
547 + LANES_TRACK_GROUP,
548 + color,
549 + ),
550 + );
551 + } else {
552 + console.timeStamp(
553 + eventIsRepeat ? '' : 'Event: ' + eventType,
554 + eventTime,
555 + updateTime > 0 ? updateTime : renderStartTime,
556 + currentTrack,
557 + LANES_TRACK_GROUP,
558 + color,
559 + );
560 + }
561 }
562 if (updateTime > 0) {
563 // Log the time from when we called setState until we started rendering.
@@ -440,18 +566,37 @@ export function logBlockingStart(
566 : includesOnlyHydrationOrOffscreenLanes(lanes)
567 ? 'tertiary-light'
568 : 'primary-light';
443 - console.timeStamp(
444 - isSpawnedUpdate
445 - ? 'Cascading Update'
446 - : renderStartTime - updateTime > 5
447 - ? 'Update Blocked'
448 - : 'Update',
449 - updateTime,
450 - renderStartTime,
451 - currentTrack,
452 - LANES_TRACK_GROUP,
453 - color,
454 - );
569 + if (__DEV__ && debugTask) {
570 + debugTask.run(
571 + // $FlowFixMe[method-unbinding]
572 + console.timeStamp.bind(
573 + console,
574 + isSpawnedUpdate
575 + ? 'Cascading Update'
576 + : renderStartTime - updateTime > 5
577 + ? 'Update Blocked'
578 + : 'Update',
579 + updateTime,
580 + renderStartTime,
581 + currentTrack,
582 + LANES_TRACK_GROUP,
583 + color,
584 + ),
585 + );
586 + } else {
587 + console.timeStamp(
588 + isSpawnedUpdate
589 + ? 'Cascading Update'
590 + : renderStartTime - updateTime > 5
591 + ? 'Update Blocked'
592 + : 'Update',
593 + updateTime,
594 + renderStartTime,
595 + currentTrack,
596 + LANES_TRACK_GROUP,
597 + color,
598 + );
599 + }
600 }
601 }
602 }
@@ -463,6 +608,7 @@ export function logTransitionStart(
608 eventType: null | string,
609 eventIsRepeat: boolean,
610 renderStartTime: number,
611 + debugTask: null | ConsoleTask, // DEV-only
612 ): void {
613 if (supportsUserTiming) {
614 currentTrack = 'Transition';
@@ -475,36 +621,82 @@ export function logTransitionStart(
621 : updateTime > 0
622 ? updateTime
623 : renderStartTime;
478 - console.timeStamp(
479 - eventIsRepeat ? '' : 'Event: ' + eventType,
480 - eventTime,
481 - endTime,
482 - currentTrack,
483 - LANES_TRACK_GROUP,
484 - color,
485 - );
624 + if (__DEV__ && debugTask) {
625 + debugTask.run(
626 + // $FlowFixMe[method-unbinding]
627 + console.timeStamp.bind(
628 + console,
629 + eventIsRepeat ? '' : 'Event: ' + eventType,
630 + eventTime,
631 + endTime,
632 + currentTrack,
633 + LANES_TRACK_GROUP,
634 + color,
635 + ),
636 + );
637 + } else {
638 + console.timeStamp(
639 + eventIsRepeat ? '' : 'Event: ' + eventType,
640 + eventTime,
641 + endTime,
642 + currentTrack,
643 + LANES_TRACK_GROUP,
644 + color,
645 + );
646 + }
647 }
648 if (startTime > 0) {
649 // Log the time from when we started an async transition until we called setState or started rendering.
489 - console.timeStamp(
490 - 'Action',
491 - startTime,
492 - updateTime > 0 ? updateTime : renderStartTime,
493 - currentTrack,
494 - LANES_TRACK_GROUP,
495 - 'primary-dark',
496 - );
650 + // TODO: Ideally this would use the debugTask of the startTransition call perhaps.
651 + if (__DEV__ && debugTask) {
652 + debugTask.run(
653 + // $FlowFixMe[method-unbinding]
654 + console.timeStamp.bind(
655 + console,
656 + 'Action',
657 + startTime,
658 + updateTime > 0 ? updateTime : renderStartTime,
659 + currentTrack,
660 + LANES_TRACK_GROUP,
661 + 'primary-dark',
662 + ),
663 + );
664 + } else {
665 + console.timeStamp(
666 + 'Action',
667 + startTime,
668 + updateTime > 0 ? updateTime : renderStartTime,
669 + currentTrack,
670 + LANES_TRACK_GROUP,
671 + 'primary-dark',
672 + );
673 + }
674 }
675 if (updateTime > 0) {
676 // Log the time from when we called setState until we started rendering.
500 - console.timeStamp(
501 - renderStartTime - updateTime > 5 ? 'Update Blocked' : 'Update',
502 - updateTime,
503 - renderStartTime,
504 - currentTrack,
505 - LANES_TRACK_GROUP,
506 - 'primary-light',
507 - );
677 + if (__DEV__ && debugTask) {
678 + debugTask.run(
679 + // $FlowFixMe[method-unbinding]
680 + console.timeStamp.bind(
681 + console,
682 + renderStartTime - updateTime > 5 ? 'Update Blocked' : 'Update',
683 + updateTime,
684 + renderStartTime,
685 + currentTrack,
686 + LANES_TRACK_GROUP,
687 + 'primary-light',
688 + ),
689 + );
690 + } else {
691 + console.timeStamp(
692 + renderStartTime - updateTime > 5 ? 'Update Blocked' : 'Update',
693 + updateTime,
694 + renderStartTime,
695 + currentTrack,
696 + LANES_TRACK_GROUP,
697 + 'primary-light',
698 + );
699 + }
700 }
701 }
702 }
packages/react-reconciler/src/ReactFiberReconciler.js
+1 -1
@@ -443,7 +443,7 @@ function updateContainerImpl(
443
444 const root = enqueueUpdate(rootFiber, update, lane);
445 if (root !== null) {
446 - startUpdateTimerByLane(lane);
446 + startUpdateTimerByLane(lane, 'root.render()');
447 scheduleUpdateOnFiber(root, rootFiber, lane);
448 entangleTransitions(root, rootFiber, lane);
449 }
packages/react-reconciler/src/ReactFiberWorkLoop.js
+4
@@ -264,6 +264,7 @@ import {
264 import {
265 blockingClampTime,
266 blockingUpdateTime,
267 + blockingUpdateTask,
268 blockingEventTime,
269 blockingEventType,
270 blockingEventIsRepeat,
@@ -272,6 +273,7 @@ import {
273 transitionClampTime,
274 transitionStartTime,
275 transitionUpdateTime,
276 + transitionUpdateTask,
277 transitionEventTime,
278 transitionEventType,
279 transitionEventIsRepeat,
@@ -1914,6 +1916,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1916 blockingSpawnedUpdate,
1917 renderStartTime,
1918 lanes,
1919 + blockingUpdateTask,
1920 );
1921 clearBlockingTimers();
1922 }
@@ -1950,6 +1953,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1953 transitionEventType,
1954 transitionEventIsRepeat,
1955 renderStartTime,
1956 + transitionUpdateTask,
1957 );
1958 clearTransitionTimers();
1959 }
packages/react-reconciler/src/ReactProfilerTimer.js
+15 -1
@@ -41,6 +41,13 @@ import * as Scheduler from 'scheduler';
41
42 const {unstable_now: now} = Scheduler;
43
44 +const createTask =
45 + // eslint-disable-next-line react-internal/no-production-logging
46 + __DEV__ && console.createTask
47 + ? // eslint-disable-next-line react-internal/no-production-logging
48 + console.createTask
49 + : (name: string) => null;
50 +
51 export let renderStartTime: number = -0;
52 export let commitStartTime: number = -0;
53 export let commitEndTime: number = -0;
@@ -54,6 +61,7 @@ export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
61
62 export let blockingClampTime: number = -0;
63 export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
64 +export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
65 export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
66 export let blockingEventType: null | string = null; // Event type of the first setState.
67 export let blockingEventIsRepeat: boolean = false;
@@ -63,6 +71,7 @@ export let blockingSuspendedTime: number = -1.1;
71 export let transitionClampTime: number = -0;
72 export let transitionStartTime: number = -1.1; // First startTransition call before setState.
73 export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
74 +export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace.
75 export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
76 export let transitionEventType: null | string = null; // Event type of the first transition.
77 export let transitionEventIsRepeat: boolean = false;
@@ -79,13 +88,14 @@ export function startYieldTimer(reason: SuspendedReason) {
88 yieldReason = reason;
89 }
90
82 -export function startUpdateTimerByLane(lane: Lane): void {
91 +export function startUpdateTimerByLane(lane: Lane, method: string): void {
92 if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
93 return;
94 }
95 if (isSyncLane(lane) || isBlockingLane(lane)) {
96 if (blockingUpdateTime < 0) {
97 blockingUpdateTime = now();
98 + blockingUpdateTask = createTask(method);
99 if (isAlreadyRendering()) {
100 blockingSpawnedUpdate = true;
101 }
@@ -108,6 +118,7 @@ export function startUpdateTimerByLane(lane: Lane): void {
118 } else if (isTransitionLane(lane)) {
119 if (transitionUpdateTime < 0) {
120 transitionUpdateTime = now();
121 + transitionUpdateTask = createTask(method);
122 if (transitionStartTime < 0) {
123 const newEventTime = resolveEventTimeStamp();
124 const newEventType = resolveEventType();
@@ -155,6 +166,7 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
166
167 export function clearBlockingTimers(): void {
168 blockingUpdateTime = -1.1;
169 + blockingUpdateTask = null;
170 blockingSuspendedTime = -1.1;
171 blockingEventIsRepeat = true;
172 blockingSpawnedUpdate = false;
@@ -194,6 +206,7 @@ export function startActionStateUpdate(): void {
206 }
207 if (transitionUpdateTime < 0) {
208 transitionUpdateTime = ACTION_STATE_MARKER;
209 + transitionUpdateTask = null;
210 }
211 }
212
@@ -204,6 +217,7 @@ export function clearAsyncTransitionTimer(): void {
217 export function clearTransitionTimers(): void {
218 transitionStartTime = -1.1;
219 transitionUpdateTime = -1.1;
220 + transitionUpdateTask = null;
221 transitionSuspendedTime = -1.1;
222 transitionEventIsRepeat = true;
223 }