Skip to content

Commit 0de1233

Browse files
[Fiber] Mark error boundaries and commit phases when an error is thrown (facebook#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 <[email protected]>
1 parent 1e9eb95 commit 0de1233

File tree

4 files changed

+262
-34
lines changed

4 files changed

+262
-34
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import {
9898
Cloned,
9999
PerformedWork,
100100
ForceClientRender,
101+
DidCapture,
101102
} from './ReactFiberFlags';
102103
import {
103104
commitStartTime,
@@ -107,14 +108,17 @@ import {
107108
resetComponentEffectTimers,
108109
pushComponentEffectStart,
109110
popComponentEffectStart,
111+
pushComponentEffectErrors,
112+
popComponentEffectErrors,
110113
componentEffectStartTime,
111114
componentEffectEndTime,
112115
componentEffectDuration,
116+
componentEffectErrors,
113117
} from './ReactProfilerTimer';
114118
import {
115119
logComponentRender,
120+
logComponentErrored,
116121
logComponentEffect,
117-
logSuspenseBoundaryClientRendered,
118122
} from './ReactFiberPerformanceTrack';
119123
import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
120124
import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';
@@ -395,7 +399,7 @@ function commitLayoutEffectOnFiber(
395399
committedLanes: Lanes,
396400
): void {
397401
const prevEffectStart = pushComponentEffectStart();
398-
402+
const prevEffectErrors = pushComponentEffectErrors();
399403
// When updating this function, also update reappearLayoutEffects, which does
400404
// most of the same things when an offscreen tree goes from hidden -> visible.
401405
const flags = finishedWork.flags;
@@ -631,10 +635,12 @@ function commitLayoutEffectOnFiber(
631635
componentEffectStartTime,
632636
componentEffectEndTime,
633637
componentEffectDuration,
638+
componentEffectErrors,
634639
);
635640
}
636641

637642
popComponentEffectStart(prevEffectStart);
643+
popComponentEffectErrors(prevEffectErrors);
638644
}
639645

640646
function abortRootTransitions(
@@ -1627,7 +1633,7 @@ function commitMutationEffectsOnFiber(
16271633
lanes: Lanes,
16281634
) {
16291635
const prevEffectStart = pushComponentEffectStart();
1630-
1636+
const prevEffectErrors = pushComponentEffectErrors();
16311637
const current = finishedWork.alternate;
16321638
const flags = finishedWork.flags;
16331639

@@ -2136,10 +2142,12 @@ function commitMutationEffectsOnFiber(
21362142
componentEffectStartTime,
21372143
componentEffectEndTime,
21382144
componentEffectDuration,
2145+
componentEffectErrors,
21392146
);
21402147
}
21412148

21422149
popComponentEffectStart(prevEffectStart);
2150+
popComponentEffectErrors(prevEffectErrors);
21432151
}
21442152

21452153
function commitReconciliationEffects(finishedWork: Fiber) {
@@ -2212,7 +2220,7 @@ function recursivelyTraverseLayoutEffects(
22122220

22132221
export function disappearLayoutEffects(finishedWork: Fiber) {
22142222
const prevEffectStart = pushComponentEffectStart();
2215-
2223+
const prevEffectErrors = pushComponentEffectErrors();
22162224
switch (finishedWork.tag) {
22172225
case FunctionComponent:
22182226
case ForwardRef:
@@ -2285,10 +2293,12 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
22852293
componentEffectStartTime,
22862294
componentEffectEndTime,
22872295
componentEffectDuration,
2296+
componentEffectErrors,
22882297
);
22892298
}
22902299

22912300
popComponentEffectStart(prevEffectStart);
2301+
popComponentEffectErrors(prevEffectErrors);
22922302
}
22932303

22942304
function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
@@ -2310,7 +2320,7 @@ export function reappearLayoutEffects(
23102320
includeWorkInProgressEffects: boolean,
23112321
) {
23122322
const prevEffectStart = pushComponentEffectStart();
2313-
2323+
const prevEffectErrors = pushComponentEffectErrors();
23142324
// Turn on layout effects in a tree that previously disappeared.
23152325
const flags = finishedWork.flags;
23162326
switch (finishedWork.tag) {
@@ -2461,10 +2471,12 @@ export function reappearLayoutEffects(
24612471
componentEffectStartTime,
24622472
componentEffectEndTime,
24632473
componentEffectDuration,
2474+
componentEffectErrors,
24642475
);
24652476
}
24662477

24672478
popComponentEffectStart(prevEffectStart);
2479+
popComponentEffectErrors(prevEffectErrors);
24682480
}
24692481

24702482
function recursivelyTraverseReappearLayoutEffects(
@@ -2701,26 +2713,7 @@ function commitPassiveMountOnFiber(
27012713
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
27022714
): void {
27032715
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();
27242717
// When updating this function, also update reconnectPassiveEffects, which does
27252718
// most of the same things when an offscreen tree goes from hidden -> visible,
27262719
// or when toggling effects inside a hidden tree.
@@ -2729,6 +2722,25 @@ function commitPassiveMountOnFiber(
27292722
case FunctionComponent:
27302723
case ForwardRef:
27312724
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+
27322744
recursivelyTraversePassiveMountEffects(
27332745
finishedRoot,
27342746
finishedWork,
@@ -2744,6 +2756,45 @@ function commitPassiveMountOnFiber(
27442756
}
27452757
break;
27462758
}
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+
}
27472798
case HostRoot: {
27482799
const prevEffectDuration = pushNestedEffectDurations();
27492800

@@ -2891,7 +2942,7 @@ function commitPassiveMountOnFiber(
28912942
// rendered boundary. Such as postpone.
28922943
if (hydrationErrors !== null) {
28932944
const startTime: number = (finishedWork.actualStartTime: any);
2894-
logSuspenseBoundaryClientRendered(
2945+
logComponentErrored(
28952946
finishedWork,
28962947
startTime,
28972948
endTime,
@@ -3074,10 +3125,12 @@ function commitPassiveMountOnFiber(
30743125
componentEffectStartTime,
30753126
componentEffectEndTime,
30763127
componentEffectDuration,
3128+
componentEffectErrors,
30773129
);
30783130
}
30793131

30803132
popComponentEffectStart(prevEffectStart);
3133+
popComponentEffectErrors(prevEffectErrors);
30813134
}
30823135

30833136
function recursivelyTraverseReconnectPassiveEffects(
@@ -3137,7 +3190,7 @@ export function reconnectPassiveEffects(
31373190
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
31383191
) {
31393192
const prevEffectStart = pushComponentEffectStart();
3140-
3193+
const prevEffectErrors = pushComponentEffectErrors();
31413194
// If this component rendered in Profiling mode (DEV or in Profiler component) then log its
31423195
// render time. We do this after the fact in the passive effect to avoid the overhead of this
31433196
// getting in the way of the render characteristics and avoid the overhead of unwinding
@@ -3331,10 +3384,12 @@ export function reconnectPassiveEffects(
33313384
componentEffectStartTime,
33323385
componentEffectEndTime,
33333386
componentEffectDuration,
3387+
componentEffectErrors,
33343388
);
33353389
}
33363390

33373391
popComponentEffectStart(prevEffectStart);
3392+
popComponentEffectErrors(prevEffectErrors);
33383393
}
33393394

33403395
function recursivelyTraverseAtomicPassiveEffects(
@@ -3611,7 +3666,7 @@ function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
36113666

36123667
function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
36133668
const prevEffectStart = pushComponentEffectStart();
3614-
3669+
const prevEffectErrors = pushComponentEffectErrors();
36153670
switch (finishedWork.tag) {
36163671
case FunctionComponent:
36173672
case ForwardRef:
@@ -3696,10 +3751,12 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
36963751
componentEffectStartTime,
36973752
componentEffectEndTime,
36983753
componentEffectDuration,
3754+
componentEffectErrors,
36993755
);
37003756
}
37013757

37023758
popComponentEffectStart(prevEffectStart);
3759+
popComponentEffectErrors(prevEffectErrors);
37033760
}
37043761

37053762
function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
@@ -3819,7 +3876,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
38193876
nearestMountedAncestor: Fiber | null,
38203877
): void {
38213878
const prevEffectStart = pushComponentEffectStart();
3822-
3879+
const prevEffectErrors = pushComponentEffectErrors();
38233880
switch (current.tag) {
38243881
case FunctionComponent:
38253882
case ForwardRef:
@@ -3946,10 +4003,12 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
39464003
componentEffectStartTime,
39474004
componentEffectEndTime,
39484005
componentEffectDuration,
4006+
componentEffectErrors,
39494007
);
39504008
}
39514009

39524010
popComponentEffectStart(prevEffectStart);
4011+
popComponentEffectErrors(prevEffectErrors);
39534012
}
39544013

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

0 commit comments

Comments
 (0)