@@ -2356,14 +2356,14 @@ function commitBeforeMutationEffects(firstChild: Fiber) {
23562356 invokeGuardedCallback ( null , commitBeforeMutationEffectsImpl , null , fiber ) ;
23572357 if ( hasCaughtError ( ) ) {
23582358 const error = clearCaughtError ( ) ;
2359- captureCommitPhaseError ( fiber , error ) ;
2359+ captureCommitPhaseError ( fiber , error , null ) ;
23602360 }
23612361 resetCurrentDebugFiberInDEV ( ) ;
23622362 } else {
23632363 try {
23642364 commitBeforeMutationEffectsImpl ( fiber ) ;
23652365 } catch ( error ) {
2366- captureCommitPhaseError ( fiber , error ) ;
2366+ captureCommitPhaseError ( fiber , error , null ) ;
23672367 }
23682368 }
23692369 fiber = fiber . sibling ;
@@ -2453,14 +2453,14 @@ function commitMutationEffects(
24532453 ) ;
24542454 if ( hasCaughtError ( ) ) {
24552455 const error = clearCaughtError ( ) ;
2456- captureCommitPhaseError ( fiber , error ) ;
2456+ captureCommitPhaseError ( fiber , error , null ) ;
24572457 }
24582458 resetCurrentDebugFiberInDEV ( ) ;
24592459 } else {
24602460 try {
24612461 commitMutationEffectsImpl ( fiber , root , renderPriorityLevel ) ;
24622462 } catch ( error ) {
2463- captureCommitPhaseError ( fiber , error ) ;
2463+ captureCommitPhaseError ( fiber , error , null ) ;
24642464 }
24652465 }
24662466 fiber = fiber . sibling ;
@@ -2556,13 +2556,13 @@ function commitMutationEffectsDeletions(
25562556 ) ;
25572557 if ( hasCaughtError ( ) ) {
25582558 const error = clearCaughtError ( ) ;
2559- captureCommitPhaseError ( childToDelete , error ) ;
2559+ captureCommitPhaseError ( childToDelete , error , null ) ;
25602560 }
25612561 } else {
25622562 try {
25632563 commitDeletion ( root , childToDelete , renderPriorityLevel ) ;
25642564 } catch ( error ) {
2565- captureCommitPhaseError ( childToDelete , error ) ;
2565+ captureCommitPhaseError ( childToDelete , error , null ) ;
25662566 }
25672567 }
25682568 }
@@ -2604,14 +2604,14 @@ function commitLayoutEffects(
26042604 ) ;
26052605 if ( hasCaughtError ( ) ) {
26062606 const error = clearCaughtError ( ) ;
2607- captureCommitPhaseError ( fiber , error ) ;
2607+ captureCommitPhaseError ( fiber , error , null ) ;
26082608 }
26092609 resetCurrentDebugFiberInDEV ( ) ;
26102610 } else {
26112611 try {
26122612 commitLayoutEffectsImpl ( fiber , root , committedLanes ) ;
26132613 } catch ( error ) {
2614- captureCommitPhaseError ( fiber , error ) ;
2614+ captureCommitPhaseError ( fiber , error , null ) ;
26152615 }
26162616 }
26172617 fiber = fiber . sibling ;
@@ -2750,7 +2750,7 @@ function flushPassiveMountEffectsImpl(fiber: Fiber): void {
27502750 if ( hasCaughtError ( ) ) {
27512751 invariant ( fiber !== null , 'Should be working on an effect.' ) ;
27522752 const error = clearCaughtError ( ) ;
2753- captureCommitPhaseError ( fiber , error ) ;
2753+ captureCommitPhaseError ( fiber , error , null ) ;
27542754 }
27552755 } else {
27562756 try {
@@ -2771,7 +2771,7 @@ function flushPassiveMountEffectsImpl(fiber: Fiber): void {
27712771 }
27722772 } catch ( error ) {
27732773 invariant ( fiber !== null , 'Should be working on an effect.' ) ;
2774- captureCommitPhaseError ( fiber , error ) ;
2774+ captureCommitPhaseError ( fiber , error , null ) ;
27752775 }
27762776 }
27772777 }
@@ -2790,7 +2790,7 @@ function flushPassiveUnmountEffects(firstChild: Fiber): void {
27902790 if ( deletions !== null ) {
27912791 for ( let i = 0 ; i < deletions . length ; i ++ ) {
27922792 const fiberToDelete = deletions [ i ] ;
2793- flushPassiveUnmountEffectsInsideOfDeletedTree ( fiberToDelete ) ;
2793+ flushPassiveUnmountEffectsInsideOfDeletedTree ( fiberToDelete , fiber ) ;
27942794
27952795 // Now that passive effects have been processed, it's safe to detach lingering pointers.
27962796 detachFiberAfterEffects ( fiberToDelete ) ;
@@ -2816,7 +2816,11 @@ function flushPassiveUnmountEffects(firstChild: Fiber): void {
28162816 case Block : {
28172817 const primaryEffectTag = fiber . effectTag & Passive ;
28182818 if ( primaryEffectTag !== NoEffect ) {
2819- flushPassiveUnmountEffectsImpl ( fiber , HookPassive | HookHasEffect ) ;
2819+ flushPassiveUnmountEffectsImpl (
2820+ fiber ,
2821+ HookPassive | HookHasEffect ,
2822+ null ,
2823+ ) ;
28202824 }
28212825 }
28222826 }
@@ -2827,6 +2831,7 @@ function flushPassiveUnmountEffects(firstChild: Fiber): void {
28272831
28282832function flushPassiveUnmountEffectsInsideOfDeletedTree (
28292833 fiberToDelete : Fiber ,
2834+ nearestMountedAncestor : Fiber ,
28302835) : void {
28312836 if ( ( fiberToDelete . subtreeTag & PassiveStaticSubtreeTag ) !== NoSubtreeTag ) {
28322837 // If any children have passive effects then traverse the subtree.
@@ -2835,7 +2840,10 @@ function flushPassiveUnmountEffectsInsideOfDeletedTree(
28352840 // since that would not cover passive effects in siblings.
28362841 let child = fiberToDelete . child ;
28372842 while ( child !== null ) {
2838- flushPassiveUnmountEffectsInsideOfDeletedTree ( child ) ;
2843+ flushPassiveUnmountEffectsInsideOfDeletedTree (
2844+ child ,
2845+ nearestMountedAncestor ,
2846+ ) ;
28392847 child = child . sibling ;
28402848 }
28412849 }
@@ -2846,7 +2854,11 @@ function flushPassiveUnmountEffectsInsideOfDeletedTree(
28462854 case ForwardRef :
28472855 case SimpleMemoComponent :
28482856 case Block : {
2849- flushPassiveUnmountEffectsImpl ( fiberToDelete , HookPassive ) ;
2857+ flushPassiveUnmountEffectsImpl (
2858+ fiberToDelete ,
2859+ HookPassive ,
2860+ nearestMountedAncestor ,
2861+ ) ;
28502862 }
28512863 }
28522864 }
@@ -2857,6 +2869,7 @@ function flushPassiveUnmountEffectsImpl(
28572869 // Tags to check for when deciding whether to unmount. e.g. to skip over
28582870 // layout effects
28592871 hookEffectTag : HookEffectTag ,
2872+ nearestMountedAncestor : Fiber | null ,
28602873) : void {
28612874 const updateQueue : FunctionComponentUpdateQueue | null = ( fiber . updateQueue : any ) ;
28622875 const lastEffect = updateQueue !== null ? updateQueue . lastEffect : null ;
@@ -2877,10 +2890,10 @@ function flushPassiveUnmountEffectsImpl(
28772890 fiber . mode & ProfileMode
28782891 ) {
28792892 startPassiveEffectTimer ( ) ;
2880- safelyCallDestroy ( fiber , destroy ) ;
2893+ safelyCallDestroy ( fiber , destroy , nearestMountedAncestor ) ;
28812894 recordPassiveEffectDuration ( fiber ) ;
28822895 } else {
2883- safelyCallDestroy ( fiber , destroy ) ;
2896+ safelyCallDestroy ( fiber , destroy , nearestMountedAncestor ) ;
28842897 }
28852898 }
28862899 }
@@ -3013,7 +3026,11 @@ function captureCommitPhaseErrorOnRoot(
30133026 }
30143027}
30153028
3016- export function captureCommitPhaseError ( sourceFiber : Fiber , error : mixed ) {
3029+ export function captureCommitPhaseError (
3030+ sourceFiber : Fiber ,
3031+ error : mixed ,
3032+ nearestMountedAncestor : Fiber | null ,
3033+ ) {
30173034 if ( sourceFiber . tag === HostRoot ) {
30183035 // Error was thrown at the root. There is no parent, so the root
30193036 // itself should capture it.
@@ -3022,6 +3039,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
30223039 }
30233040
30243041 let fiber = sourceFiber . return ;
3042+ let foundErrorBoundary = false ;
30253043 while ( fiber !== null ) {
30263044 if ( fiber . tag === HostRoot ) {
30273045 captureCommitPhaseErrorOnRoot ( fiber , sourceFiber , error ) ;
@@ -3034,6 +3052,8 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
30343052 ( typeof instance . componentDidCatch === 'function' &&
30353053 ! isAlreadyFailedLegacyErrorBoundary ( instance ) )
30363054 ) {
3055+ foundErrorBoundary = true ;
3056+
30373057 const errorInfo = createCapturedValue ( error , sourceFiber ) ;
30383058 const update = createClassErrorUpdate (
30393059 fiber ,
@@ -3047,12 +3067,49 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
30473067 markRootUpdated ( root , SyncLane , eventTime ) ;
30483068 ensureRootIsScheduled ( root , eventTime ) ;
30493069 schedulePendingInteractions ( root , SyncLane ) ;
3070+ } else {
3071+ // This component has already been unmounted.
3072+ // We can't schedule any follow up work for the root because the fiber is already unmounted,
3073+ // but we can still call the log-only boundary so the error isn't swallowed.
3074+ // TODO We will need to rethink this for function component boundaries
3075+ if (
3076+ typeof instance . componentDidCatch === 'function' &&
3077+ ! isAlreadyFailedLegacyErrorBoundary ( instance )
3078+ ) {
3079+ try {
3080+ instance . componentDidCatch ( error , errorInfo ) ;
3081+ } catch { }
3082+ }
30503083 }
30513084 return ;
30523085 }
30533086 }
30543087 fiber = fiber . return ;
30553088 }
3089+
3090+ if ( ! foundErrorBoundary ) {
3091+ if ( nearestMountedAncestor !== null ) {
3092+ // An error was thrown inside of an unmounted subtree that contained no error boundaries.
3093+ // Bubble up to the nearest still-mounted error boundary for loggin purposes.
3094+ fiber = nearestMountedAncestor ;
3095+ while ( fiber !== null ) {
3096+ if ( fiber . tag === ClassComponent ) {
3097+ const instance = fiber . stateNode ;
3098+ if (
3099+ typeof instance . componentDidCatch === 'function' &&
3100+ ! isAlreadyFailedLegacyErrorBoundary ( instance )
3101+ ) {
3102+ const errorInfo = createCapturedValue ( error , sourceFiber ) ;
3103+ try {
3104+ instance . componentDidCatch ( error , errorInfo ) ;
3105+ } catch { }
3106+ return ;
3107+ }
3108+ }
3109+ fiber = fiber . return ;
3110+ }
3111+ }
3112+ }
30563113}
30573114
30583115export function pingSuspendedRoot (
0 commit comments