@@ -14,6 +14,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
1414import type { StackCursor } from './ReactFiberStack.new' ;
1515import type { Flags } from './ReactFiberFlags' ;
1616import type { FunctionComponentUpdateQueue } from './ReactFiberHooks.new' ;
17+ import type { EventPriority } from './ReactEventPriorities.new' ;
1718
1819import {
1920 warnAboutDeprecatedLifecycles ,
@@ -297,7 +298,11 @@ let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes;
297298let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes;
298299// Lanes that were pinged (in an interleaved event) during this render.
299300let workInProgressRootPingedLanes: Lanes = NoLanes;
301+ // Errors that are thrown during the render phase.
300302let workInProgressRootConcurrentErrors: Array< mixed > | null = null;
303+ // These are errors that we recovered from without surfacing them to the UI.
304+ // We will log them once the tree commits.
305+ let workInProgressRootRecoverableErrors: Array< mixed > | null = null;
301306
302307// The most recent time we committed a fallback. This lets us ensure a train
303308// model where we don't commit new loading states in too quick succession.
@@ -896,16 +901,21 @@ function recoverFromConcurrentError(root, errorRetryLanes) {
896901 }
897902 }
898903
904+ const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
899905 const exitStatus = renderRootSync(root, errorRetryLanes);
900- const recoverableErrors = workInProgressRootConcurrentErrors;
901906 if (exitStatus !== RootErrored) {
902- // Successfully finished rendering
903- if ( recoverableErrors !== null ) {
904- // Although we recovered the UI without surfacing an error, we should
905- // still log the errors so they can be fixed.
906- for ( let j = 0 ; j < recoverableErrors . length ; j ++ ) {
907- const recoverableError = recoverableErrors [ j ] ;
908- logRecoverableError ( root . errorLoggingConfig , recoverableError ) ;
907+ // Successfully finished rendering on retry
908+ if ( errorsFromFirstAttempt !== null ) {
909+ // The errors from the failed first attempt have been recovered. Add
910+ // them to the collection of recoverable errors. We'll log them in the
911+ // commit phase.
912+ if ( workInProgressRootConcurrentErrors === null ) {
913+ workInProgressRootRecoverableErrors = errorsFromFirstAttempt ;
914+ } else {
915+ workInProgressRootConcurrentErrors = workInProgressRootConcurrentErrors . push . apply (
916+ null ,
917+ errorsFromFirstAttempt ,
918+ ) ;
909919 }
910920 }
911921 } else {
@@ -929,7 +939,7 @@ function finishConcurrentRender(root, exitStatus, lanes) {
929939 case RootErrored : {
930940 // We should have already attempted to retry this tree. If we reached
931941 // this point, it errored again. Commit it.
932- commitRoot ( root ) ;
942+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
933943 break ;
934944 }
935945 case RootSuspended : {
@@ -969,14 +979,14 @@ function finishConcurrentRender(root, exitStatus, lanes) {
969979 // lower priority work to do. Instead of committing the fallback
970980 // immediately, wait for more data to arrive.
971981 root . timeoutHandle = scheduleTimeout (
972- commitRoot . bind ( null , root ) ,
982+ commitRoot . bind ( null , root , workInProgressRootRecoverableErrors ) ,
973983 msUntilTimeout ,
974984 ) ;
975985 break ;
976986 }
977987 }
978988 // The work expired. Commit immediately.
979- commitRoot ( root ) ;
989+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
980990 break ;
981991 }
982992 case RootSuspendedWithDelay : {
@@ -1007,20 +1017,20 @@ function finishConcurrentRender(root, exitStatus, lanes) {
10071017 // Instead of committing the fallback immediately, wait for more data
10081018 // to arrive.
10091019 root . timeoutHandle = scheduleTimeout (
1010- commitRoot . bind ( null , root ) ,
1020+ commitRoot . bind ( null , root , workInProgressRootRecoverableErrors ) ,
10111021 msUntilTimeout ,
10121022 ) ;
10131023 break ;
10141024 }
10151025 }
10161026
10171027 // Commit the placeholder.
1018- commitRoot ( root ) ;
1028+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
10191029 break ;
10201030 }
10211031 case RootCompleted : {
10221032 // The work completed. Ready to commit.
1023- commitRoot ( root ) ;
1033+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
10241034 break ;
10251035 }
10261036 default: {
@@ -1140,7 +1150,7 @@ function performSyncWorkOnRoot(root) {
11401150 const finishedWork: Fiber = (root.current.alternate: any);
11411151 root.finishedWork = finishedWork;
11421152 root.finishedLanes = lanes;
1143- commitRoot(root);
1153+ commitRoot(root, workInProgressRootRecoverableErrors );
11441154
11451155 // Before exiting, make sure there's a callback scheduled for the next
11461156 // pending level.
@@ -1337,6 +1347,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
13371347 workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
13381348 workInProgressRootPingedLanes = NoLanes;
13391349 workInProgressRootConcurrentErrors = null;
1350+ workInProgressRootRecoverableErrors = null;
13401351
13411352 enqueueInterleavedUpdates();
13421353
@@ -1803,15 +1814,15 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
18031814 }
18041815}
18051816
1806- function commitRoot ( root ) {
1817+ function commitRoot ( root : FiberRoot , recoverableErrors : null | Array < mixed > ) {
18071818 // TODO: This no longer makes any sense. We already wrap the mutation and
18081819 // layout phases. Should be able to remove.
18091820 const previousUpdateLanePriority = getCurrentUpdatePriority ( ) ;
18101821 const prevTransition = ReactCurrentBatchConfig . transition ;
18111822 try {
18121823 ReactCurrentBatchConfig. transition = 0 ;
18131824 setCurrentUpdatePriority ( DiscreteEventPriority ) ;
1814- commitRootImpl ( root , previousUpdateLanePriority ) ;
1825+ commitRootImpl ( root , recoverableErrors , previousUpdateLanePriority ) ;
18151826 } finally {
18161827 ReactCurrentBatchConfig . transition = prevTransition ;
18171828 setCurrentUpdatePriority ( previousUpdateLanePriority ) ;
@@ -1820,7 +1831,11 @@ function commitRoot(root) {
18201831 return null ;
18211832}
18221833
1823- function commitRootImpl ( root , renderPriorityLevel ) {
1834+ function commitRootImpl (
1835+ root : FiberRoot ,
1836+ recoverableErrors : null | Array < mixed > ,
1837+ renderPriorityLevel : EventPriority ,
1838+ ) {
18241839 do {
18251840 // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
18261841 // means `flushPassiveEffects` will sometimes result in additional
@@ -2091,6 +2106,15 @@ function commitRootImpl(root, renderPriorityLevel) {
20912106 // additional work on this root is scheduled.
20922107 ensureRootIsScheduled ( root , now ( ) ) ;
20932108
2109+ if ( recoverableErrors !== null ) {
2110+ // There were errors during this render, but recovered from them without
2111+ // needing to surface it to the UI. We log them here.
2112+ for ( let i = 0 ; i < recoverableErrors . length ; i ++ ) {
2113+ const recoverableError = recoverableErrors [ i ] ;
2114+ logRecoverableError ( root . errorLoggingConfig , recoverableError ) ;
2115+ }
2116+ }
2117+
20942118 if ( hasUncaughtError ) {
20952119 hasUncaughtError = false ;
20962120 const error = firstUncaughtError ;
0 commit comments