@samitouri / QOS-React / commits / 0de1233fd1

[Fiber] Mark error boundaries and commit phases when an error is thrown (#31876)

This tracks commit phase errors and marks the component that errored as red. These also get the errors attached to the entry. <img width="1505" alt="Screenshot 2024-12-20 at 2 40 14 PM" src="https://github.com/user-attachments/assets/cac3ead7-a024-4e33-ab27-2e95293c4299" /> In the render phase I just mark the Error Boundary that caught the error. We don't have access to the actual error since it's locked behind closures in the update queue. We could probably expose that someway. <img width="949" alt="Screenshot 2024-12-20 at 1 49 05 PM" src="https://github.com/user-attachments/assets/3032455d-d9f2-462b-9c07-7be23663ecd3" /> Follow ups: Since the Error Boundary doesn't commit its attempted render, we don't log those. If we did then maybe we should just mark the errored component like I do for the commit phase. We could potentially walk the list of errors and log the captured fibers and just log their entries as children. We could also potentially walk the uncommitted Fiber tree by stashing it somewhere or even getting it from the alternate. This could be done on Suspense boundaries too to track failed hydrations. --------- Co-authored-by: Ricky <rickhanlonii@gmail.com>

Sebastian Markbåge committed Jan 2, 2025 at 13:28 UTC 0de1233fd180969f7ffdfc98151922f2466ceb1f
4 files changed +262 -34
packages/react-reconciler/src/ReactFiberCommitWork.js
+88 -29
@@ -98,6 +98,7 @@ import {
98 Cloned,
99 PerformedWork,
100 ForceClientRender,
101 + DidCapture,
102 } from './ReactFiberFlags';
103 import {
104 commitStartTime,
@@ -107,14 +108,17 @@ import {
108 resetComponentEffectTimers,
109 pushComponentEffectStart,
110 popComponentEffectStart,
111 + pushComponentEffectErrors,
112 + popComponentEffectErrors,
113 componentEffectStartTime,
114 componentEffectEndTime,
115 componentEffectDuration,
116 + componentEffectErrors,
117 } from './ReactProfilerTimer';
118 import {
119 logComponentRender,
120 + logComponentErrored,
121 logComponentEffect,
117 - logSuspenseBoundaryClientRendered,
122 } from './ReactFiberPerformanceTrack';
123 import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
124 import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';
@@ -395,7 +399,7 @@ function commitLayoutEffectOnFiber(
399 committedLanes: Lanes,
400 ): void {
401 const prevEffectStart = pushComponentEffectStart();
398 -
402 + const prevEffectErrors = pushComponentEffectErrors();
403 // When updating this function, also update reappearLayoutEffects, which does
404 // most of the same things when an offscreen tree goes from hidden -> visible.
405 const flags = finishedWork.flags;
@@ -631,10 +635,12 @@ function commitLayoutEffectOnFiber(
635 componentEffectStartTime,
636 componentEffectEndTime,
637 componentEffectDuration,
638 + componentEffectErrors,
639 );
640 }
641
642 popComponentEffectStart(prevEffectStart);
643 + popComponentEffectErrors(prevEffectErrors);
644 }
645
646 function abortRootTransitions(
@@ -1627,7 +1633,7 @@ function commitMutationEffectsOnFiber(
1633 lanes: Lanes,
1634 ) {
1635 const prevEffectStart = pushComponentEffectStart();
1630 -
1636 + const prevEffectErrors = pushComponentEffectErrors();
1637 const current = finishedWork.alternate;
1638 const flags = finishedWork.flags;
1639
@@ -2136,10 +2142,12 @@ function commitMutationEffectsOnFiber(
2142 componentEffectStartTime,
2143 componentEffectEndTime,
2144 componentEffectDuration,
2145 + componentEffectErrors,
2146 );
2147 }
2148
2149 popComponentEffectStart(prevEffectStart);
2150 + popComponentEffectErrors(prevEffectErrors);
2151 }
2152
2153 function commitReconciliationEffects(finishedWork: Fiber) {
@@ -2212,7 +2220,7 @@ function recursivelyTraverseLayoutEffects(
2220
2221 export function disappearLayoutEffects(finishedWork: Fiber) {
2222 const prevEffectStart = pushComponentEffectStart();
2215 -
2223 + const prevEffectErrors = pushComponentEffectErrors();
2224 switch (finishedWork.tag) {
2225 case FunctionComponent:
2226 case ForwardRef:
@@ -2285,10 +2293,12 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
2293 componentEffectStartTime,
2294 componentEffectEndTime,
2295 componentEffectDuration,
2296 + componentEffectErrors,
2297 );
2298 }
2299
2300 popComponentEffectStart(prevEffectStart);
2301 + popComponentEffectErrors(prevEffectErrors);
2302 }
2303
2304 function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
@@ -2310,7 +2320,7 @@ export function reappearLayoutEffects(
2320 includeWorkInProgressEffects: boolean,
2321 ) {
2322 const prevEffectStart = pushComponentEffectStart();
2313 -
2323 + const prevEffectErrors = pushComponentEffectErrors();
2324 // Turn on layout effects in a tree that previously disappeared.
2325 const flags = finishedWork.flags;
2326 switch (finishedWork.tag) {
@@ -2461,10 +2471,12 @@ export function reappearLayoutEffects(
2471 componentEffectStartTime,
2472 componentEffectEndTime,
2473 componentEffectDuration,
2474 + componentEffectErrors,
2475 );
2476 }
2477
2478 popComponentEffectStart(prevEffectStart);
2479 + popComponentEffectErrors(prevEffectErrors);
2480 }
2481
2482 function recursivelyTraverseReappearLayoutEffects(
@@ -2701,26 +2713,7 @@ function commitPassiveMountOnFiber(
2713 endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
2714 ): void {
2715 const prevEffectStart = pushComponentEffectStart();
2704 -
2705 - // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
2706 - // render time. We do this after the fact in the passive effect to avoid the overhead of this
2707 - // getting in the way of the render characteristics and avoid the overhead of unwinding
2708 - // uncommitted renders.
2709 - if (
2710 - enableProfilerTimer &&
2711 - enableComponentPerformanceTrack &&
2712 - (finishedWork.mode & ProfileMode) !== NoMode &&
2713 - ((finishedWork.actualStartTime: any): number) > 0 &&
2714 - (finishedWork.flags & PerformedWork) !== NoFlags
2715 - ) {
2716 - logComponentRender(
2717 - finishedWork,
2718 - ((finishedWork.actualStartTime: any): number),
2719 - endTime,
2720 - inHydratedSubtree,
2721 - );
2722 - }
2723 -
2716 + const prevEffectErrors = pushComponentEffectErrors();
2717 // When updating this function, also update reconnectPassiveEffects, which does
2718 // most of the same things when an offscreen tree goes from hidden -> visible,
2719 // or when toggling effects inside a hidden tree.
@@ -2729,6 +2722,25 @@ function commitPassiveMountOnFiber(
2722 case FunctionComponent:
2723 case ForwardRef:
2724 case SimpleMemoComponent: {
2725 + // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
2726 + // render time. We do this after the fact in the passive effect to avoid the overhead of this
2727 + // getting in the way of the render characteristics and avoid the overhead of unwinding
2728 + // uncommitted renders.
2729 + if (
2730 + enableProfilerTimer &&
2731 + enableComponentPerformanceTrack &&
2732 + (finishedWork.mode & ProfileMode) !== NoMode &&
2733 + ((finishedWork.actualStartTime: any): number) > 0 &&
2734 + (finishedWork.flags & PerformedWork) !== NoFlags
2735 + ) {
2736 + logComponentRender(
2737 + finishedWork,
2738 + ((finishedWork.actualStartTime: any): number),
2739 + endTime,
2740 + inHydratedSubtree,
2741 + );
2742 + }
2743 +
2744 recursivelyTraversePassiveMountEffects(
2745 finishedRoot,
2746 finishedWork,
@@ -2744,6 +2756,45 @@ function commitPassiveMountOnFiber(
2756 }
2757 break;
2758 }
2759 + case ClassComponent: {
2760 + // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
2761 + // render time. We do this after the fact in the passive effect to avoid the overhead of this
2762 + // getting in the way of the render characteristics and avoid the overhead of unwinding
2763 + // uncommitted renders.
2764 + if (
2765 + enableProfilerTimer &&
2766 + enableComponentPerformanceTrack &&
2767 + (finishedWork.mode & ProfileMode) !== NoMode &&
2768 + ((finishedWork.actualStartTime: any): number) > 0
2769 + ) {
2770 + if ((finishedWork.flags & DidCapture) !== NoFlags) {
2771 + logComponentErrored(
2772 + finishedWork,
2773 + ((finishedWork.actualStartTime: any): number),
2774 + endTime,
2775 + // TODO: The captured values are all hidden inside the updater/callback closures so
2776 + // we can't get to the errors but they're there so we should be able to log them.
2777 + [],
2778 + );
2779 + } else if ((finishedWork.flags & PerformedWork) !== NoFlags) {
2780 + logComponentRender(
2781 + finishedWork,
2782 + ((finishedWork.actualStartTime: any): number),
2783 + endTime,
2784 + inHydratedSubtree,
2785 + );
2786 + }
2787 + }
2788 +
2789 + recursivelyTraversePassiveMountEffects(
2790 + finishedRoot,
2791 + finishedWork,
2792 + committedLanes,
2793 + committedTransitions,
2794 + endTime,
2795 + );
2796 + break;
2797 + }
2798 case HostRoot: {
2799 const prevEffectDuration = pushNestedEffectDurations();
2800
@@ -2891,7 +2942,7 @@ function commitPassiveMountOnFiber(
2942 // rendered boundary. Such as postpone.
2943 if (hydrationErrors !== null) {
2944 const startTime: number = (finishedWork.actualStartTime: any);
2894 - logSuspenseBoundaryClientRendered(
2945 + logComponentErrored(
2946 finishedWork,
2947 startTime,
2948 endTime,
@@ -3074,10 +3125,12 @@ function commitPassiveMountOnFiber(
3125 componentEffectStartTime,
3126 componentEffectEndTime,
3127 componentEffectDuration,
3128 + componentEffectErrors,
3129 );
3130 }
3131
3132 popComponentEffectStart(prevEffectStart);
3133 + popComponentEffectErrors(prevEffectErrors);
3134 }
3135
3136 function recursivelyTraverseReconnectPassiveEffects(
@@ -3137,7 +3190,7 @@ export function reconnectPassiveEffects(
3190 endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
3191 ) {
3192 const prevEffectStart = pushComponentEffectStart();
3140 -
3193 + const prevEffectErrors = pushComponentEffectErrors();
3194 // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
3195 // render time. We do this after the fact in the passive effect to avoid the overhead of this
3196 // getting in the way of the render characteristics and avoid the overhead of unwinding
@@ -3331,10 +3384,12 @@ export function reconnectPassiveEffects(
3384 componentEffectStartTime,
3385 componentEffectEndTime,
3386 componentEffectDuration,
3387 + componentEffectErrors,
3388 );
3389 }
3390
3391 popComponentEffectStart(prevEffectStart);
3392 + popComponentEffectErrors(prevEffectErrors);
3393 }
3394
3395 function recursivelyTraverseAtomicPassiveEffects(
@@ -3611,7 +3666,7 @@ function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
3666
3667 function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
3668 const prevEffectStart = pushComponentEffectStart();
3614 -
3669 + const prevEffectErrors = pushComponentEffectErrors();
3670 switch (finishedWork.tag) {
3671 case FunctionComponent:
3672 case ForwardRef:
@@ -3696,10 +3751,12 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
3751 componentEffectStartTime,
3752 componentEffectEndTime,
3753 componentEffectDuration,
3754 + componentEffectErrors,
3755 );
3756 }
3757
3758 popComponentEffectStart(prevEffectStart);
3759 + popComponentEffectErrors(prevEffectErrors);
3760 }
3761
3762 function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
@@ -3819,7 +3876,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
3876 nearestMountedAncestor: Fiber | null,
3877 ): void {
3878 const prevEffectStart = pushComponentEffectStart();
3822 -
3879 + const prevEffectErrors = pushComponentEffectErrors();
3880 switch (current.tag) {
3881 case FunctionComponent:
3882 case ForwardRef:
@@ -3946,10 +4003,12 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
4003 componentEffectStartTime,
4004 componentEffectEndTime,
4005 componentEffectDuration,
4006 + componentEffectErrors,
4007 );
4008 }
4009
4010 popComponentEffectStart(prevEffectStart);
4011 + popComponentEffectErrors(prevEffectErrors);
4012 }
4013
4014 export function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
packages/react-reconciler/src/ReactFiberPerformanceTrack.js
+114 -4
@@ -13,6 +13,8 @@ import type {Lanes} from './ReactFiberLane';
13
14 import type {CapturedValue} from './ReactCapturedValue';
15
16 +import {SuspenseComponent} from './ReactWorkTags';
17 +
18 import getComponentNameFromFiber from './getComponentNameFromFiber';
19
20 import {
@@ -159,13 +161,64 @@ export function logComponentRender(
161 }
162 }
163
162 -export function logSuspenseBoundaryClientRendered(
164 +export function logComponentErrored(
165 + fiber: Fiber,
166 + startTime: number,
167 + endTime: number,
168 + errors: Array<CapturedValue<mixed>>,
169 +): void {
170 + if (supportsUserTiming) {
171 + const name = getComponentNameFromFiber(fiber);
172 + if (name === null) {
173 + // Skip
174 + return;
175 + }
176 + const properties = [];
177 + if (__DEV__) {
178 + for (let i = 0; i < errors.length; i++) {
179 + const capturedValue = errors[i];
180 + const error = capturedValue.value;
181 + const message =
182 + typeof error === 'object' &&
183 + error !== null &&
184 + typeof error.message === 'string'
185 + ? // eslint-disable-next-line react-internal/safe-string-coercion
186 + String(error.message)
187 + : // eslint-disable-next-line react-internal/safe-string-coercion
188 + String(error);
189 + properties.push(['Error', message]);
190 + }
191 + }
192 + performance.measure(name, {
193 + start: startTime,
194 + end: endTime,
195 + detail: {
196 + devtools: {
197 + color: 'error',
198 + track: COMPONENTS_TRACK,
199 + tooltipText:
200 + fiber.tag === SuspenseComponent
201 + ? 'Hydration failed'
202 + : 'Error boundary caught an error',
203 + properties,
204 + },
205 + },
206 + });
207 + }
208 +}
209 +
210 +function logComponentEffectErrored(
211 fiber: Fiber,
212 startTime: number,
213 endTime: number,
214 errors: Array<CapturedValue<mixed>>,
215 ): void {
216 if (supportsUserTiming) {
217 + const name = getComponentNameFromFiber(fiber);
218 + if (name === null) {
219 + // Skip
220 + return;
221 + }
222 const properties = [];
223 if (__DEV__) {
224 for (let i = 0; i < errors.length; i++) {
@@ -182,14 +235,14 @@ export function logSuspenseBoundaryClientRendered(
235 properties.push(['Error', message]);
236 }
237 }
185 - performance.measure('Suspense', {
238 + performance.measure(name, {
239 start: startTime,
240 end: endTime,
241 detail: {
242 devtools: {
243 color: 'error',
244 track: COMPONENTS_TRACK,
192 - tooltipText: 'Hydration failed',
245 + tooltipText: 'A lifecycle or effect errored',
246 properties,
247 },
248 },
@@ -202,7 +255,12 @@ export function logComponentEffect(
255 startTime: number,
256 endTime: number,
257 selfTime: number,
258 + errors: null | Array<CapturedValue<mixed>>,
259 ): void {
260 + if (errors !== null) {
261 + logComponentEffectErrored(fiber, startTime, endTime, errors);
262 + return;
263 + }
264 const name = getComponentNameFromFiber(fiber);
265 if (name === null) {
266 // Skip
@@ -534,7 +592,54 @@ export function logSuspendedCommitPhase(
592 }
593 }
594
537 -export function logCommitPhase(startTime: number, endTime: number): void {
595 +export function logCommitErrored(
596 + startTime: number,
597 + endTime: number,
598 + errors: Array<CapturedValue<mixed>>,
599 + passive: boolean,
600 +): void {
601 + if (supportsUserTiming) {
602 + const properties = [];
603 + if (__DEV__) {
604 + for (let i = 0; i < errors.length; i++) {
605 + const capturedValue = errors[i];
606 + const error = capturedValue.value;
607 + const message =
608 + typeof error === 'object' &&
609 + error !== null &&
610 + typeof error.message === 'string'
611 + ? // eslint-disable-next-line react-internal/safe-string-coercion
612 + String(error.message)
613 + : // eslint-disable-next-line react-internal/safe-string-coercion
614 + String(error);
615 + properties.push(['Error', message]);
616 + }
617 + }
618 + performance.measure('Errored', {
619 + start: startTime,
620 + end: endTime,
621 + detail: {
622 + devtools: {
623 + color: 'error',
624 + track: reusableLaneDevToolDetails.track,
625 + trackGroup: LANES_TRACK_GROUP,
626 + tooltipText: passive ? 'Remaining Effects Errored' : 'Commit Errored',
627 + properties,
628 + },
629 + },
630 + });
631 + }
632 +}
633 +
634 +export function logCommitPhase(
635 + startTime: number,
636 + endTime: number,
637 + errors: null | Array<CapturedValue<mixed>>,
638 +): void {
639 + if (errors !== null) {
640 + logCommitErrored(startTime, endTime, errors, false);
641 + return;
642 + }
643 if (supportsUserTiming) {
644 reusableLaneDevToolDetails.color = 'secondary-dark';
645 reusableLaneOptions.start = startTime;
@@ -562,7 +667,12 @@ export function logPaintYieldPhase(
667 export function logPassiveCommitPhase(
668 startTime: number,
669 endTime: number,
670 + errors: null | Array<CapturedValue<mixed>>,
671 ): void {
672 + if (errors !== null) {
673 + logCommitErrored(startTime, endTime, errors, true);
674 + return;
675 + }
676 if (supportsUserTiming) {
677 reusableLaneDevToolDetails.color = 'secondary-dark';
678 reusableLaneOptions.start = startTime;
packages/react-reconciler/src/ReactFiberWorkLoop.js
+17 -1
@@ -253,6 +253,7 @@ import {
253 renderStartTime,
254 commitStartTime,
255 commitEndTime,
256 + commitErrors,
257 recordRenderTime,
258 recordCommitTime,
259 recordCommitEndTime,
@@ -264,6 +265,8 @@ import {
265 yieldStartTime,
266 yieldReason,
267 startPingTimerByLanes,
268 + recordEffectError,
269 + resetCommitErrors,
270 } from './ReactProfilerTimer';
271
272 // DEV stuff
@@ -3340,6 +3343,7 @@ function commitRootImpl(
3343 if (enableProfilerTimer) {
3344 // Mark the current commit time to be shared by all Profilers in this
3345 // batch. This enables them to be grouped later.
3346 + resetCommitErrors();
3347 recordCommitTime();
3348 if (enableComponentPerformanceTrack) {
3349 if (suspendedCommitReason === SUSPENDED_COMMIT) {
@@ -3433,6 +3437,7 @@ function commitRootImpl(
3437 ? completedRenderEndTime
3438 : commitStartTime,
3439 commitEndTime,
3440 + commitErrors,
3441 );
3442 }
3443
@@ -3722,6 +3727,7 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
3727
3728 let passiveEffectStartTime = 0;
3729 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3730 + resetCommitErrors();
3731 passiveEffectStartTime = now();
3732 logPaintYieldPhase(
3733 commitEndTime,
@@ -3758,7 +3764,11 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
3764
3765 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3766 const passiveEffectsEndTime = now();
3761 - logPassiveCommitPhase(passiveEffectStartTime, passiveEffectsEndTime);
3767 + logPassiveCommitPhase(
3768 + passiveEffectStartTime,
3769 + passiveEffectsEndTime,
3770 + commitErrors,
3771 + );
3772 finalizeRender(lanes, passiveEffectsEndTime);
3773 }
3774
@@ -3842,6 +3852,9 @@ function captureCommitPhaseErrorOnRoot(
3852 error: mixed,
3853 ) {
3854 const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
3855 + if (enableProfilerTimer && enableComponentPerformanceTrack) {
3856 + recordEffectError(errorInfo);
3857 + }
3858 const update = createRootErrorUpdate(
3859 rootFiber.stateNode,
3860 errorInfo,
@@ -3883,6 +3896,9 @@ export function captureCommitPhaseError(
3896 !isAlreadyFailedLegacyErrorBoundary(instance))
3897 ) {
3898 const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
3899 + if (enableProfilerTimer && enableComponentPerformanceTrack) {
3900 + recordEffectError(errorInfo);
3901 + }
3902 const update = createClassErrorUpdate((SyncLane: Lane));
3903 const root = enqueueUpdate(fiber, update, (SyncLane: Lane));
3904 if (root !== null) {
packages/react-reconciler/src/ReactProfilerTimer.js
+43
@@ -12,6 +12,9 @@ import type {Fiber} from './ReactInternalTypes';
12 import type {SuspendedReason} from './ReactFiberWorkLoop';
13
14 import type {Lane, Lanes} from './ReactFiberLane';
15 +
16 +import type {CapturedValue} from './ReactCapturedValue';
17 +
18 import {
19 isTransitionLane,
20 isBlockingLane,
@@ -41,11 +44,13 @@ const {unstable_now: now} = Scheduler;
44 export let renderStartTime: number = -0;
45 export let commitStartTime: number = -0;
46 export let commitEndTime: number = -0;
47 +export let commitErrors: null | Array<CapturedValue<mixed>> = null;
48 export let profilerStartTime: number = -1.1;
49 export let profilerEffectDuration: number = -0;
50 export let componentEffectDuration: number = -0;
51 export let componentEffectStartTime: number = -1.1;
52 export let componentEffectEndTime: number = -1.1;
53 +export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
54
55 export let blockingClampTime: number = -0;
56 export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
@@ -282,6 +287,26 @@ export function popComponentEffectStart(prevEffectStart: number): void {
287 }
288 }
289
290 +export function pushComponentEffectErrors(): null | Array<
291 + CapturedValue<mixed>,
292 +> {
293 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
294 + return null;
295 + }
296 + const prevErrors = componentEffectErrors;
297 + componentEffectErrors = null;
298 + return prevErrors;
299 +}
300 +
301 +export function popComponentEffectErrors(
302 + prevErrors: null | Array<CapturedValue<mixed>>,
303 +): void {
304 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
305 + return;
306 + }
307 + componentEffectErrors = prevErrors;
308 +}
309 +
310 /**
311 * Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
312 *
@@ -416,6 +441,24 @@ export function recordEffectDuration(fiber: Fiber): void {
441 }
442 }
443
444 +export function recordEffectError(errorInfo: CapturedValue<mixed>): void {
445 + if (!enableProfilerTimer || !enableProfilerCommitHooks) {
446 + return;
447 + }
448 + if (componentEffectErrors === null) {
449 + componentEffectErrors = [];
450 + }
451 + componentEffectErrors.push(errorInfo);
452 + if (commitErrors === null) {
453 + commitErrors = [];
454 + }
455 + commitErrors.push(errorInfo);
456 +}
457 +
458 +export function resetCommitErrors(): void {
459 + commitErrors = null;
460 +}
461 +
462 export function startEffectTimer(): void {
463 if (!enableProfilerTimer || !enableProfilerCommitHooks) {
464 return;