Skip to content

Commit ead40b2

Browse files
committed
Mark the component that errored during the commit phase
We also keep track of the errors and log those.
1 parent aeb3c41 commit ead40b2

File tree

4 files changed

+113
-8
lines changed

4 files changed

+113
-8
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,12 @@ import {
108108
resetComponentEffectTimers,
109109
pushComponentEffectStart,
110110
popComponentEffectStart,
111+
pushComponentEffectErrors,
112+
popComponentEffectErrors,
111113
componentEffectStartTime,
112114
componentEffectEndTime,
113115
componentEffectDuration,
116+
componentEffectErrors,
114117
} from './ReactProfilerTimer';
115118
import {
116119
logComponentRender,
@@ -396,7 +399,7 @@ function commitLayoutEffectOnFiber(
396399
committedLanes: Lanes,
397400
): void {
398401
const prevEffectStart = pushComponentEffectStart();
399-
402+
const prevEffectErrors = pushComponentEffectErrors();
400403
// When updating this function, also update reappearLayoutEffects, which does
401404
// most of the same things when an offscreen tree goes from hidden -> visible.
402405
const flags = finishedWork.flags;
@@ -632,10 +635,12 @@ function commitLayoutEffectOnFiber(
632635
componentEffectStartTime,
633636
componentEffectEndTime,
634637
componentEffectDuration,
638+
componentEffectErrors,
635639
);
636640
}
637641

638642
popComponentEffectStart(prevEffectStart);
643+
popComponentEffectErrors(prevEffectErrors);
639644
}
640645

641646
function abortRootTransitions(
@@ -1628,7 +1633,7 @@ function commitMutationEffectsOnFiber(
16281633
lanes: Lanes,
16291634
) {
16301635
const prevEffectStart = pushComponentEffectStart();
1631-
1636+
const prevEffectErrors = pushComponentEffectErrors();
16321637
const current = finishedWork.alternate;
16331638
const flags = finishedWork.flags;
16341639

@@ -2137,10 +2142,12 @@ function commitMutationEffectsOnFiber(
21372142
componentEffectStartTime,
21382143
componentEffectEndTime,
21392144
componentEffectDuration,
2145+
componentEffectErrors,
21402146
);
21412147
}
21422148

21432149
popComponentEffectStart(prevEffectStart);
2150+
popComponentEffectErrors(prevEffectErrors);
21442151
}
21452152

21462153
function commitReconciliationEffects(finishedWork: Fiber) {
@@ -2213,7 +2220,7 @@ function recursivelyTraverseLayoutEffects(
22132220

22142221
export function disappearLayoutEffects(finishedWork: Fiber) {
22152222
const prevEffectStart = pushComponentEffectStart();
2216-
2223+
const prevEffectErrors = pushComponentEffectErrors();
22172224
switch (finishedWork.tag) {
22182225
case FunctionComponent:
22192226
case ForwardRef:
@@ -2286,10 +2293,12 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
22862293
componentEffectStartTime,
22872294
componentEffectEndTime,
22882295
componentEffectDuration,
2296+
componentEffectErrors,
22892297
);
22902298
}
22912299

22922300
popComponentEffectStart(prevEffectStart);
2301+
popComponentEffectErrors(prevEffectErrors);
22932302
}
22942303

22952304
function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
@@ -2311,7 +2320,7 @@ export function reappearLayoutEffects(
23112320
includeWorkInProgressEffects: boolean,
23122321
) {
23132322
const prevEffectStart = pushComponentEffectStart();
2314-
2323+
const prevEffectErrors = pushComponentEffectErrors();
23152324
// Turn on layout effects in a tree that previously disappeared.
23162325
const flags = finishedWork.flags;
23172326
switch (finishedWork.tag) {
@@ -2462,10 +2471,12 @@ export function reappearLayoutEffects(
24622471
componentEffectStartTime,
24632472
componentEffectEndTime,
24642473
componentEffectDuration,
2474+
componentEffectErrors,
24652475
);
24662476
}
24672477

24682478
popComponentEffectStart(prevEffectStart);
2479+
popComponentEffectErrors(prevEffectErrors);
24692480
}
24702481

24712482
function recursivelyTraverseReappearLayoutEffects(
@@ -2702,7 +2713,7 @@ function commitPassiveMountOnFiber(
27022713
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
27032714
): void {
27042715
const prevEffectStart = pushComponentEffectStart();
2705-
2716+
const prevEffectErrors = pushComponentEffectErrors();
27062717
// When updating this function, also update reconnectPassiveEffects, which does
27072718
// most of the same things when an offscreen tree goes from hidden -> visible,
27082719
// or when toggling effects inside a hidden tree.
@@ -3114,10 +3125,12 @@ function commitPassiveMountOnFiber(
31143125
componentEffectStartTime,
31153126
componentEffectEndTime,
31163127
componentEffectDuration,
3128+
componentEffectErrors,
31173129
);
31183130
}
31193131

31203132
popComponentEffectStart(prevEffectStart);
3133+
popComponentEffectErrors(prevEffectErrors);
31213134
}
31223135

31233136
function recursivelyTraverseReconnectPassiveEffects(
@@ -3177,7 +3190,7 @@ export function reconnectPassiveEffects(
31773190
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
31783191
) {
31793192
const prevEffectStart = pushComponentEffectStart();
3180-
3193+
const prevEffectErrors = pushComponentEffectErrors();
31813194
// If this component rendered in Profiling mode (DEV or in Profiler component) then log its
31823195
// render time. We do this after the fact in the passive effect to avoid the overhead of this
31833196
// getting in the way of the render characteristics and avoid the overhead of unwinding
@@ -3371,10 +3384,12 @@ export function reconnectPassiveEffects(
33713384
componentEffectStartTime,
33723385
componentEffectEndTime,
33733386
componentEffectDuration,
3387+
componentEffectErrors,
33743388
);
33753389
}
33763390

33773391
popComponentEffectStart(prevEffectStart);
3392+
popComponentEffectErrors(prevEffectErrors);
33783393
}
33793394

33803395
function recursivelyTraverseAtomicPassiveEffects(
@@ -3651,7 +3666,7 @@ function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
36513666

36523667
function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
36533668
const prevEffectStart = pushComponentEffectStart();
3654-
3669+
const prevEffectErrors = pushComponentEffectErrors();
36553670
switch (finishedWork.tag) {
36563671
case FunctionComponent:
36573672
case ForwardRef:
@@ -3736,10 +3751,12 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
37363751
componentEffectStartTime,
37373752
componentEffectEndTime,
37383753
componentEffectDuration,
3754+
componentEffectErrors,
37393755
);
37403756
}
37413757

37423758
popComponentEffectStart(prevEffectStart);
3759+
popComponentEffectErrors(prevEffectErrors);
37433760
}
37443761

37453762
function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
@@ -3859,7 +3876,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
38593876
nearestMountedAncestor: Fiber | null,
38603877
): void {
38613878
const prevEffectStart = pushComponentEffectStart();
3862-
3879+
const prevEffectErrors = pushComponentEffectErrors();
38633880
switch (current.tag) {
38643881
case FunctionComponent:
38653882
case ForwardRef:
@@ -3986,10 +4003,12 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
39864003
componentEffectStartTime,
39874004
componentEffectEndTime,
39884005
componentEffectDuration,
4006+
componentEffectErrors,
39894007
);
39904008
}
39914009

39924010
popComponentEffectStart(prevEffectStart);
4011+
popComponentEffectErrors(prevEffectErrors);
39934012
}
39944013

39954014
export function invokeLayoutEffectMountInDEV(fiber: Fiber): void {

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,60 @@ export function logComponentErrored(
207207
}
208208
}
209209

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++) {
225+
const capturedValue = errors[i];
226+
const error = capturedValue.value;
227+
const message =
228+
typeof error === 'object' &&
229+
error !== null &&
230+
typeof error.message === 'string'
231+
? // eslint-disable-next-line react-internal/safe-string-coercion
232+
String(error.message)
233+
: // eslint-disable-next-line react-internal/safe-string-coercion
234+
String(error);
235+
properties.push(['Error', message]);
236+
}
237+
}
238+
performance.measure(name, {
239+
start: startTime,
240+
end: endTime,
241+
detail: {
242+
devtools: {
243+
color: 'error',
244+
track: COMPONENTS_TRACK,
245+
tooltipText: 'A life cycle or effect errored',
246+
properties,
247+
},
248+
},
249+
});
250+
}
251+
}
252+
210253
export function logComponentEffect(
211254
fiber: Fiber,
212255
startTime: number,
213256
endTime: number,
214257
selfTime: number,
258+
errors: null | Array<CapturedValue<mixed>>,
215259
): void {
260+
if (errors !== null) {
261+
logComponentEffectErrored(fiber, startTime, endTime, errors);
262+
return;
263+
}
216264
const name = getComponentNameFromFiber(fiber);
217265
if (name === null) {
218266
// Skip

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ import {
262262
yieldStartTime,
263263
yieldReason,
264264
startPingTimerByLanes,
265+
recordEffectError,
265266
} from './ReactProfilerTimer';
266267

267268
// DEV stuff
@@ -3823,6 +3824,9 @@ function captureCommitPhaseErrorOnRoot(
38233824
error: mixed,
38243825
) {
38253826
const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
3827+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3828+
recordEffectError(errorInfo);
3829+
}
38263830
const update = createRootErrorUpdate(
38273831
rootFiber.stateNode,
38283832
errorInfo,
@@ -3864,6 +3868,9 @@ export function captureCommitPhaseError(
38643868
!isAlreadyFailedLegacyErrorBoundary(instance))
38653869
) {
38663870
const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
3871+
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3872+
recordEffectError(errorInfo);
3873+
}
38673874
const update = createClassErrorUpdate((SyncLane: Lane));
38683875
const root = enqueueUpdate(fiber, update, (SyncLane: Lane));
38693876
if (root !== null) {

packages/react-reconciler/src/ReactProfilerTimer.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import type {Fiber} from './ReactInternalTypes';
1212
import type {SuspendedReason} from './ReactFiberWorkLoop';
1313

1414
import type {Lane, Lanes} from './ReactFiberLane';
15+
16+
import type {CapturedValue} from './ReactCapturedValue';
17+
1518
import {
1619
isTransitionLane,
1720
isBlockingLane,
@@ -44,6 +47,7 @@ export let profilerEffectDuration: number = -0;
4447
export let componentEffectDuration: number = -0;
4548
export let componentEffectStartTime: number = -1.1;
4649
export let componentEffectEndTime: number = -1.1;
50+
export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
4751

4852
export let blockingClampTime: number = -0;
4953
export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
@@ -270,6 +274,26 @@ export function popComponentEffectStart(prevEffectStart: number): void {
270274
}
271275
}
272276

277+
export function pushComponentEffectErrors(): null | Array<
278+
CapturedValue<mixed>,
279+
> {
280+
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
281+
return null;
282+
}
283+
const prevErrors = componentEffectErrors;
284+
componentEffectErrors = null;
285+
return prevErrors;
286+
}
287+
288+
export function popComponentEffectErrors(
289+
prevErrors: null | Array<CapturedValue<mixed>>,
290+
): void {
291+
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
292+
return;
293+
}
294+
componentEffectErrors = prevErrors;
295+
}
296+
273297
/**
274298
* Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
275299
*
@@ -404,6 +428,13 @@ export function recordEffectDuration(fiber: Fiber): void {
404428
}
405429
}
406430

431+
export function recordEffectError(errorInfo: CapturedValue<mixed>): void {
432+
if (componentEffectErrors === null) {
433+
componentEffectErrors = [];
434+
}
435+
componentEffectErrors.push(errorInfo);
436+
}
437+
407438
export function startEffectTimer(): void {
408439
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
409440
return;

0 commit comments

Comments
 (0)