@@ -14,6 +14,7 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
14
14
import type { StackCursor } from './ReactFiberStack.new' ;
15
15
import type { Flags } from './ReactFiberFlags' ;
16
16
import type { FunctionComponentUpdateQueue } from './ReactFiberHooks.new' ;
17
+ import type { EventPriority } from './ReactEventPriorities.new' ;
17
18
18
19
import {
19
20
warnAboutDeprecatedLifecycles ,
@@ -297,7 +298,11 @@ let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes;
297
298
let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes;
298
299
// Lanes that were pinged (in an interleaved event) during this render.
299
300
let workInProgressRootPingedLanes: Lanes = NoLanes;
301
+ // Errors that are thrown during the render phase.
300
302
let 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;
301
306
302
307
// The most recent time we committed a fallback. This lets us ensure a train
303
308
// model where we don't commit new loading states in too quick succession.
@@ -896,16 +901,21 @@ function recoverFromConcurrentError(root, errorRetryLanes) {
896
901
}
897
902
}
898
903
904
+ const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
899
905
const exitStatus = renderRootSync(root, errorRetryLanes);
900
- const recoverableErrors = workInProgressRootConcurrentErrors;
901
906
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
+ ) ;
909
919
}
910
920
}
911
921
} else {
@@ -929,7 +939,7 @@ function finishConcurrentRender(root, exitStatus, lanes) {
929
939
case RootErrored : {
930
940
// We should have already attempted to retry this tree. If we reached
931
941
// this point, it errored again. Commit it.
932
- commitRoot ( root ) ;
942
+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
933
943
break ;
934
944
}
935
945
case RootSuspended : {
@@ -969,14 +979,14 @@ function finishConcurrentRender(root, exitStatus, lanes) {
969
979
// lower priority work to do. Instead of committing the fallback
970
980
// immediately, wait for more data to arrive.
971
981
root . timeoutHandle = scheduleTimeout (
972
- commitRoot . bind ( null , root ) ,
982
+ commitRoot . bind ( null , root , workInProgressRootRecoverableErrors ) ,
973
983
msUntilTimeout ,
974
984
) ;
975
985
break ;
976
986
}
977
987
}
978
988
// The work expired. Commit immediately.
979
- commitRoot ( root ) ;
989
+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
980
990
break ;
981
991
}
982
992
case RootSuspendedWithDelay : {
@@ -1007,20 +1017,20 @@ function finishConcurrentRender(root, exitStatus, lanes) {
1007
1017
// Instead of committing the fallback immediately, wait for more data
1008
1018
// to arrive.
1009
1019
root . timeoutHandle = scheduleTimeout (
1010
- commitRoot . bind ( null , root ) ,
1020
+ commitRoot . bind ( null , root , workInProgressRootRecoverableErrors ) ,
1011
1021
msUntilTimeout ,
1012
1022
) ;
1013
1023
break ;
1014
1024
}
1015
1025
}
1016
1026
1017
1027
// Commit the placeholder.
1018
- commitRoot ( root ) ;
1028
+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
1019
1029
break ;
1020
1030
}
1021
1031
case RootCompleted : {
1022
1032
// The work completed. Ready to commit.
1023
- commitRoot ( root ) ;
1033
+ commitRoot ( root , workInProgressRootRecoverableErrors ) ;
1024
1034
break ;
1025
1035
}
1026
1036
default: {
@@ -1140,7 +1150,7 @@ function performSyncWorkOnRoot(root) {
1140
1150
const finishedWork: Fiber = (root.current.alternate: any);
1141
1151
root.finishedWork = finishedWork;
1142
1152
root.finishedLanes = lanes;
1143
- commitRoot(root);
1153
+ commitRoot(root, workInProgressRootRecoverableErrors );
1144
1154
1145
1155
// Before exiting, make sure there's a callback scheduled for the next
1146
1156
// pending level.
@@ -1337,6 +1347,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
1337
1347
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
1338
1348
workInProgressRootPingedLanes = NoLanes;
1339
1349
workInProgressRootConcurrentErrors = null;
1350
+ workInProgressRootRecoverableErrors = null;
1340
1351
1341
1352
enqueueInterleavedUpdates();
1342
1353
@@ -1803,15 +1814,15 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
1803
1814
}
1804
1815
}
1805
1816
1806
- function commitRoot ( root ) {
1817
+ function commitRoot ( root : FiberRoot , recoverableErrors : null | Array < mixed > ) {
1807
1818
// TODO: This no longer makes any sense. We already wrap the mutation and
1808
1819
// layout phases. Should be able to remove.
1809
1820
const previousUpdateLanePriority = getCurrentUpdatePriority ( ) ;
1810
1821
const prevTransition = ReactCurrentBatchConfig . transition ;
1811
1822
try {
1812
1823
ReactCurrentBatchConfig. transition = 0 ;
1813
1824
setCurrentUpdatePriority ( DiscreteEventPriority ) ;
1814
- commitRootImpl ( root , previousUpdateLanePriority ) ;
1825
+ commitRootImpl ( root , recoverableErrors , previousUpdateLanePriority ) ;
1815
1826
} finally {
1816
1827
ReactCurrentBatchConfig . transition = prevTransition ;
1817
1828
setCurrentUpdatePriority ( previousUpdateLanePriority ) ;
@@ -1820,7 +1831,11 @@ function commitRoot(root) {
1820
1831
return null ;
1821
1832
}
1822
1833
1823
- function commitRootImpl ( root , renderPriorityLevel ) {
1834
+ function commitRootImpl (
1835
+ root : FiberRoot ,
1836
+ recoverableErrors : null | Array < mixed > ,
1837
+ renderPriorityLevel : EventPriority ,
1838
+ ) {
1824
1839
do {
1825
1840
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
1826
1841
// means `flushPassiveEffects` will sometimes result in additional
@@ -2091,6 +2106,15 @@ function commitRootImpl(root, renderPriorityLevel) {
2091
2106
// additional work on this root is scheduled.
2092
2107
ensureRootIsScheduled ( root , now ( ) ) ;
2093
2108
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
+
2094
2118
if ( hasUncaughtError ) {
2095
2119
hasUncaughtError = false ;
2096
2120
const error = firstUncaughtError ;
0 commit comments