96
recordScheduleUpdate,
97
startWorkTimer,
98
stopWorkTimer,
99
+ stopFailedWorkTimer,
100
startWorkLoopTimer,
101
stopWorkLoopTimer,
102
startCommitTimer,
183
// Might be less confusing.
184
let priorityContext: PriorityLevel = NoWork;
185
185
- // Keep track of this so we can reset the priority context if an error
186
- // is thrown during reconciliation.
187
- let priorityContextBeforeReconciliation: PriorityLevel = NoWork;
188
-
186
// Keeps track of whether we're currently in a work loop.
187
let isPerformingWork: boolean = false;
188
223
// Error boundaries that captured an error during the current commit.
224
let commitPhaseBoundaries: Set<Fiber> | null = null;
225
let firstUncaughtError: Error | null = null;
229
- let fatalError: Error | null = null;
226
+ let didFatal: boolean = false;
227
228
let isCommitting: boolean = false;
229
let isUnmounting: boolean = false;
230
231
// Use these to prevent an infinite loop of nested updates
235
- let nestedSyncUpdates = 0;
236
- let NESTED_SYNC_UPDATE_LIMIT = 1000;
232
+ const NESTED_UPDATE_LIMIT = 1000;
233
+ let nestedUpdateCount = 0;
234
235
function resetContextStack() {
236
// Reset the stack
240
resetHostContainer();
241
}
242
246
- // findNextUnitOfWork mutates the current priority context. It is reset after
247
- // after the workLoop exits, so never call findNextUnitOfWork from outside
243
+ // resetNextUnitOfWork mutates the current priority context. It is reset after
244
+ // after the workLoop exits, so never call resetNextUnitOfWork from outside
245
// the work loop.
249
- function findNextUnitOfWork() {
246
+ function resetNextUnitOfWork() {
247
// Clear out roots with no more work on them, or if they have uncaught errors
248
while (
249
nextScheduledRoot !== null &&
284
}
285
if (highestPriorityRoot !== null) {
286
nextPriorityLevel = highestPriorityLevel;
290
- priorityContext = nextPriorityLevel;
291
-
292
- if (
293
- nextPriorityLevel === TaskPriority ||
294
- nextPriorityLevel === SynchronousPriority
295
- ) {
296
- invariant(
297
- nestedSyncUpdates++ <= NESTED_SYNC_UPDATE_LIMIT,
298
- 'Maximum update depth exceeded. This can happen when a ' +
299
- 'component repeatedly calls setState inside componentWillUpdate or ' +
300
- 'componentDidUpdate. React limits the number of nested updates to ' +
301
- 'prevent infinite loops.',
302
- );
303
- }
304
-
287
// Before we start any new work, let's make sure that we have a fresh
288
// stack to work from.
289
// TODO: This call is buried a bit too deep. It would be nice to have
291
// unfortunately this is it.
292
resetContextStack();
293
312
- return createWorkInProgress(
294
+ nextUnitOfWork = createWorkInProgress(
295
highestPriorityRoot.current,
296
highestPriorityLevel,
297
);
298
+ return;
299
}
300
301
nextPriorityLevel = NoWork;
319
- return null;
302
+ nextUnitOfWork = null;
303
+ return;
304
}
305
306
function commitAllHostEffects() {
429
'in React. Please file an issue.',
430
);
431
432
+ if (
433
+ nextPriorityLevel === SynchronousPriority ||
434
+ nextPriorityLevel === TaskPriority
435
+ ) {
436
+ // Keep track of the number of iterations to prevent an infinite
437
+ // update loop.
438
+ nestedUpdateCount++;
439
+ }
440
+
441
// Reset this to null before calling lifecycles
442
ReactCurrentOwner.current = null;
443
451
- // Updates that occur during the commit phase should have Task priority
452
- const previousPriorityContext = priorityContext;
453
- priorityContext = TaskPriority;
454
-
444
let firstEffect;
445
if (finishedWork.effectTag > PerformedWork) {
446
// A fiber's effect list consists only of its children, not itself. So if
554
commitPhaseBoundaries = null;
555
}
556
568
- priorityContext = previousPriorityContext;
557
+ // This tree is done. Reset the unit of work pointer to the next highest
558
+ // priority root. If there's no more work left, the pointer is set to null.
559
+ resetNextUnitOfWork();
560
}
561
562
function resetWorkPriority(
659
workInProgress = returnFiber;
660
continue;
661
} else {
671
- // We've reached the root. Unless we're current performing deferred
672
- // work, we should commit the completed work immediately. If we are
673
- // performing deferred work, returning null indicates to the caller
674
- // that we just completed the root so they can handle that case correctly.
675
- if (nextPriorityLevel < HighPriority) {
676
- // Otherwise, we should commit immediately.
677
- commitAllWork(workInProgress);
678
- } else {
679
- pendingCommit = workInProgress;
680
- }
662
+ // We've reached the root. Mark the root as pending commit. Depending
663
+ // on how much time we have left, we'll either commit it now or in
664
+ // the next frame.
665
+ pendingCommit = workInProgress;
666
return null;
667
}
668
}
731
return next;
732
}
733
749
- function clearErrors() {
750
- if (nextUnitOfWork === null) {
751
- nextUnitOfWork = findNextUnitOfWork();
752
- }
753
- // Keep performing work until there are no more errors
754
- while (
755
- capturedErrors !== null &&
756
- capturedErrors.size &&
757
- nextUnitOfWork !== null &&
758
- nextPriorityLevel !== NoWork &&
759
- nextPriorityLevel <= TaskPriority
760
- ) {
761
- if (hasCapturedError(nextUnitOfWork)) {
762
- // Use a forked version of performUnitOfWork
763
- nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
764
- } else {
765
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
766
- }
767
- if (nextUnitOfWork === null) {
768
- // If performUnitOfWork returns null, that means we just committed
769
- // a root. Normally we'd need to clear any errors that were scheduled
770
- // during the commit phase. But we're already clearing errors, so
771
- // we can continue.
772
- nextUnitOfWork = findNextUnitOfWork();
734
+ function performDeferredWork(deadline: Deadline) {
735
+ performWork(OffscreenPriority, deadline);
736
+ }
737
+
738
+ function handleCommitPhaseErrors() {
739
+ // This is a special work loop for handling commit phase errors. It's
740
+ // similar to the syncrhonous work loop, but does an additional check on
741
+ // each fiber to see if it's an error boundary with an unhandled error. If
742
+ // so, it uses a forked version of performUnitOfWork that unmounts the
743
+ // failed subtree.
744
+ //
745
+ // The loop stops once the children have unmounted and error lifecycles are
746
+ // called. Then we return to the regular flow.
747
+
748
+ if (capturedErrors !== null && capturedErrors.size > 0) {
749
+ while (nextUnitOfWork !== null) {
750
+ if (hasCapturedError(nextUnitOfWork)) {
751
+ // Use a forked version of performUnitOfWork
752
+ nextUnitOfWork = performFailedUnitOfWork(nextUnitOfWork);
753
+ } else {
754
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
755
+ }
756
+ if (nextUnitOfWork === null) {
757
+ invariant(
758
+ pendingCommit !== null,
759
+ 'Should have a pending commit. This error is likely caused by ' +
760
+ 'a bug in React. Please file an issue.',
761
+ );
762
+ // We just completed a root. Commit it now.
763
+ priorityContext = TaskPriority;
764
+ commitAllWork(pendingCommit);
765
+ priorityContext = nextPriorityLevel;
766
+
767
+ if (capturedErrors === null || capturedErrors.size === 0) {
768
+ // There are no more unhandled errors. We can exit this special
769
+ // work loop. If there's still additional work, we'll perform it
770
+ // using one of the normal work loops.
771
+ break;
772
+ }
773
+ // The commit phase produced additional errors. Continue working.
774
+ invariant(
775
+ nextPriorityLevel === TaskPriority,
776
+ 'Commit phase errors should be scheduled to recover with task ' +
777
+ 'priority. This error is likely caused by a bug in React. ' +
778
+ 'Please file an issue.',
779
+ );
780
+ }
781
}
782
}
783
}
784
777
- function workLoopAsync(minPriorityLevel: PriorityLevel, deadline: Deadline) {
778
- // Flush asynchronous work until the deadline expires.
779
- while (nextUnitOfWork !== null && !deadlineHasExpired) {
780
- if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
781
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
782
- // In a deferred work batch, iff nextUnitOfWork returns null, we just
783
- // completed a root and a pendingCommit exists. Logically, we could
784
- // omit either of the checks in the following condition, but we need
785
- // both to satisfy Flow.
786
- if (nextUnitOfWork === null && pendingCommit !== null) {
787
- // If we have time, we should commit the work now.
788
- if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
785
+ function workLoop(
786
+ minPriorityLevel: PriorityLevel,
787
+ deadline: Deadline | null,
788
+ ) {
789
+ if (pendingCommit !== null) {
790
+ priorityContext = TaskPriority;
791
+ commitAllWork(pendingCommit);
792
+ handleCommitPhaseErrors();
793
+ } else if (nextUnitOfWork === null) {
794
+ resetNextUnitOfWork();
795
+ }
796
+
797
+ if (nextPriorityLevel === NoWork || nextPriorityLevel > minPriorityLevel) {
798
+ return;
799
+ }
800
+
801
+ // During the render phase, updates should have the same priority at which
802
+ // we're rendering.
803
+ priorityContext = nextPriorityLevel;
804
+
805
+ loop: do {
806
+ if (nextPriorityLevel <= TaskPriority) {
807
+ // Flush all synchronous and task work.
808
+ while (nextUnitOfWork !== null) {
809
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
810
+ if (nextUnitOfWork === null) {
811
+ invariant(
812
+ pendingCommit !== null,
813
+ 'Should have a pending commit. This error is likely caused by ' +
814
+ 'a bug in React. Please file an issue.',
815
+ );
816
+ // We just completed a root. Commit it now.
817
+ priorityContext = TaskPriority;
818
commitAllWork(pendingCommit);
790
- nextUnitOfWork = findNextUnitOfWork();
819
+ priorityContext = nextPriorityLevel;
820
// Clear any errors that were scheduled during the commit phase.
792
- clearErrors();
821
+ handleCommitPhaseErrors();
822
// The priority level may have changed. Check again.
823
if (
824
nextPriorityLevel === NoWork ||
825
nextPriorityLevel > minPriorityLevel ||
797
- nextPriorityLevel < HighPriority
826
+ nextPriorityLevel > TaskPriority
827
) {
828
// The priority level does not match.
829
break;
830
}
831
+ }
832
+ }
833
+ } else if (deadline !== null) {
834
+ // Flush asynchronous work until the deadline expires.
835
+ while (nextUnitOfWork !== null && !deadlineHasExpired) {
836
+ if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
837
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
838
+ // In a deferred work batch, iff nextUnitOfWork returns null, we just
839
+ // completed a root and a pendingCommit exists. Logically, we could
840
+ // omit either of the checks in the following condition, but we need
841
+ // both to satisfy Flow.
842
+ if (nextUnitOfWork === null) {
843
+ invariant(
844
+ pendingCommit !== null,
845
+ 'Should have a pending commit. This error is likely caused by ' +
846
+ 'a bug in React. Please file an issue.',
847
+ );
848
+ // We just completed a root. If we have time, commit it now.
849
+ // Otherwise, we'll commit it in the next frame.
850
+ if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
851
+ priorityContext = TaskPriority;
852
+ commitAllWork(pendingCommit);
853
+ priorityContext = nextPriorityLevel;
854
+ // Clear any errors that were scheduled during the commit phase.
855
+ handleCommitPhaseErrors();
856
+ // The priority level may have changed. Check again.
857
+ if (
858
+ nextPriorityLevel === NoWork ||
859
+ nextPriorityLevel > minPriorityLevel ||
860
+ nextPriorityLevel < HighPriority
861
+ ) {
862
+ // The priority level does not match.
863
+ break;
864
+ }
865
+ } else {
866
+ deadlineHasExpired = true;
867
+ }
868
+ }
869
} else {
870
deadlineHasExpired = true;
871
}
805
- // Otherwise the root will committed in the next frame.
872
}
807
- } else {
808
- deadlineHasExpired = true;
873
}
810
- }
811
- }
874
813
- function workLoopSync(minPriorityLevel: PriorityLevel) {
814
- // Flush all synchronous and task work.
815
- while (nextUnitOfWork !== null) {
816
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
817
- if (nextUnitOfWork === null) {
818
- nextUnitOfWork = findNextUnitOfWork();
819
- // performUnitOfWork returned null, which means we just committed a
820
- // root. Clear any errors that were scheduled during the commit phase.
821
- clearErrors();
822
- // The priority level may have changed. Check again.
823
- if (
824
- nextPriorityLevel === NoWork ||
825
- nextPriorityLevel > minPriorityLevel ||
826
- nextPriorityLevel > TaskPriority
827
- ) {
828
- // The priority level does not match.
829
- break;
830
- }
875
+ // There might be work left. Depending on the priority, we should
876
+ // either perform it now or schedule a callback to perform it later.
877
+ switch (nextPriorityLevel) {
878
+ case SynchronousPriority:
879
+ case TaskPriority:
880
+ // We have remaining synchronous or task work. Keep performing it,
881
+ // regardless of whether we're inside a callback.
882
+ if (nextPriorityLevel <= minPriorityLevel) {
883
+ continue loop;
884
+ }
885
+ break loop;
886
+ case HighPriority:
887
+ case LowPriority:
888
+ case OffscreenPriority:
889
+ // We have remaining async work.
890
+ if (deadline === null) {
891
+ // We're not inside a callback. Exit and perform the work during
892
+ // the next callback.
893
+ break loop;
894
+ }
895
+ // We are inside a callback.
896
+ if (!deadlineHasExpired && nextPriorityLevel <= minPriorityLevel) {
897
+ // We still have time. Keep working.
898
+ continue loop;
899
+ }
900
+ // We've run out of time. Exit.
901
+ break loop;
902
+ case NoWork:
903
+ // No work left. We can exit.
904
+ break loop;
905
+ default:
906
+ invariant(false, 'Switch statement should be exhuastive.');
907
}
832
- }
908
+ } while (true);
909
}
910
835
- function workLoop(
911
+ function performWorkCatchBlock(
912
+ failedWork: Fiber,
913
+ boundary: Fiber,
914
minPriorityLevel: PriorityLevel,
915
deadline: Deadline | null,
916
) {
839
- // Clear any errors.
840
- clearErrors();
841
-
842
- if (nextUnitOfWork === null) {
843
- nextUnitOfWork = findNextUnitOfWork();
844
- }
845
-
846
- if (nextPriorityLevel !== NoWork && nextPriorityLevel <= minPriorityLevel) {
847
- if (nextPriorityLevel <= TaskPriority) {
848
- workLoopSync(minPriorityLevel);
849
- } else if (deadline !== null) {
850
- workLoopAsync(minPriorityLevel, deadline);
851
- }
852
- }
853
- }
854
-
855
- function performDeferredWork(deadline: Deadline) {
856
- performWork(OffscreenPriority, deadline);
917
+ // We're going to restart the error boundary that captured the error.
918
+ // Conceptually, we're unwinding the stack. We need to unwind the
919
+ // context stack, too.
920
+ unwindContexts(failedWork, boundary);
921
+
922
+ // Restart the error boundary using a forked version of
923
+ // performUnitOfWork that deletes the boundary's children. The entire
924
+ // failed subree will be unmounted. During the commit phase, a special
925
+ // lifecycle method is called on the error boundary, which triggers
926
+ // a re-render.
927
+ nextUnitOfWork = performFailedUnitOfWork(boundary);
928
+
929
+ // Continue working.
930
+ workLoop(minPriorityLevel, deadline);
931
}
932
933
function performWork(
945
);
946
isPerformingWork = true;
947
874
- let hasRemainingAsyncWork = false;
875
-
876
- // This outer loop exists so that we can restart the work loop after
877
- // catching an error. It also lets us flush Task work at the end of a
878
- // deferred batch.
879
- while (fatalError === null) {
880
- // Before starting any work, check to see if there are any pending
881
- // commits from the previous frame.
882
- // TODO: Only commit asynchronous priority at beginning or end of a frame.
883
- // Task work can be committed whenever.
884
- if (pendingCommit !== null && !deadlineHasExpired) {
885
- // Safe to call this outside the work loop because the commit phase has
886
- // its own try-catch.
887
- commitAllWork(pendingCommit);
948
+ nestedUpdateCount = 0;
949
+
950
+ // The priority context changes during the render phase. We'll need to
951
+ // reset it at the end.
952
+ const previousPriorityContext = priorityContext;
953
+
954
+ let error;
955
+ if (__DEV__) {
956
+ error = invokeGuardedCallback(
957
+ null,
958
+ workLoop,
959
+ null,
960
+ minPriorityLevel,
961
+ deadline,
962
+ );
963
+ } else {
964
+ try {
965
+ workLoop(minPriorityLevel, deadline);
966
+ error = null;
967
+ } catch (e) {
968
+ error = e;
969
+ }
970
+ }
971
+
972
+ // An error was thrown during the render phase.
973
+ while (error !== null) {
974
+ if (didFatal) {
975
+ // This was a fatal error. Don't attempt to recover from it.
976
+ firstUncaughtError = error;
977
+ break;
978
+ }
979
+
980
+ const failedWork = nextUnitOfWork;
981
+ if (failedWork === null) {
982
+ // An error was thrown but there's no current unit of work. This can
983
+ // happen during the commit phase if there's a bug in the renderer.
984
+ didFatal = true;
985
+ continue;
986
+ }
987
+
988
+ // "Capture" the error by finding the nearest boundary. If there is no
989
+ // error boundary, we use the root.
990
+ const boundary = captureError(failedWork, error);
991
+ invariant(
992
+ boundary !== null,
993
+ 'Should have found an error boundary. This error is likely ' +
994
+ 'caused by a bug in React. Please file an issue.',
995
+ );
996
+
997
+ if (didFatal) {
998
+ // The error we just captured was a fatal error. This happens
999
+ // when the error propagates to the root more than once.
1000
+ continue;
1001
}
1002
890
- // Nothing in performWork should be allowed to throw. All unsafe
891
- // operations must happen within workLoop, which is extracted to a
892
- // separate function so that it can be optimized by the JS engine.
893
- priorityContextBeforeReconciliation = priorityContext;
894
- let error = null;
1003
if (__DEV__) {
1004
error = invokeGuardedCallback(
1005
null,
898
- workLoop,
1006
+ performWorkCatchBlock,
1007
null,
1008
+ failedWork,
1009
+ boundary,
1010
minPriorityLevel,
1011
deadline,
1012
);
1013
} else {
1014
try {
905
- workLoop(minPriorityLevel, deadline);
1015
+ performWorkCatchBlock(
1016
+ failedWork,
1017
+ boundary,
1018
+ minPriorityLevel,
1019
+ deadline,
1020
+ );
1021
+ error = null;
1022
} catch (e) {
1023
error = e;
1024
}
1025
}
910
- // Reset the priority context to its value before reconcilation.
911
- priorityContext = priorityContextBeforeReconciliation;
1026
1027
if (error !== null) {
914
- // We caught an error during either the begin or complete phases.
915
- const failedWork = nextUnitOfWork;
916
-
917
- if (failedWork !== null) {
918
- // "Capture" the error by finding the nearest boundary. If there is no
919
- // error boundary, the nearest host container acts as one. If
920
- // captureError returns null, the error was intentionally ignored.
921
- const maybeBoundary = captureError(failedWork, error);
922
- if (maybeBoundary !== null) {
923
- const boundary = maybeBoundary;
924
-
925
- // Complete the boundary as if it rendered null. This will unmount
926
- // the failed tree.
927
- beginFailedWork(boundary.alternate, boundary, nextPriorityLevel);
928
-
929
- // The next unit of work is now the boundary that captured the error.
930
- // Conceptually, we're unwinding the stack. We need to unwind the
931
- // context stack, too, from the failed work to the boundary that
932
- // captured the error.
933
- // TODO: If we set the memoized props in beginWork instead of
934
- // completeWork, rather than unwind the stack, we can just restart
935
- // from the root. Can't do that until then because without memoized
936
- // props, the nodes higher up in the tree will rerender unnecessarily.
937
- unwindContexts(failedWork, boundary);
938
- nextUnitOfWork = completeUnitOfWork(boundary);
939
- }
940
- // Continue performing work
941
- continue;
942
- } else if (fatalError === null) {
943
- // There is no current unit of work. This is a worst-case scenario
944
- // and should only be possible if there's a bug in the renderer, e.g.
945
- // inside resetAfterCommit.
946
- fatalError = error;
947
- }
948
- } else {
949
- // There might be work left. Depending on the priority, we should
950
- // either perform it now or schedule a callback to perform it later.
951
- switch (nextPriorityLevel) {
952
- case SynchronousPriority:
953
- case TaskPriority:
954
- // We have remaining synchronous or task work. Keep performing it,
955
- // regardless of whether we're inside a callback.
956
- if (nextPriorityLevel <= minPriorityLevel) {
957
- continue;
958
- }
959
- break;
960
- case HighPriority:
961
- case LowPriority:
962
- case OffscreenPriority:
963
- // We have remaining async work.
964
- if (deadline === null) {
965
- // We're not inside a callback. Exit and perform the work during
966
- // the next callback.
967
- hasRemainingAsyncWork = true;
968
- } else {
969
- // We are inside a callback.
970
- if (
971
- !deadlineHasExpired &&
972
- nextPriorityLevel <= minPriorityLevel
973
- ) {
974
- // We still have time. Keep working.
975
- continue;
976
- }
977
- // We've run out of time. Exit.
978
- hasRemainingAsyncWork = true;
979
- }
980
- break;
981
- case NoWork:
982
- // No work left. We can exit.
983
- break;
984
- default:
985
- invariant(false, 'Switch statement should be exhuastive.');
986
- }
987
- // Exit the loop.
988
- break;
1028
+ // Another error was thrown during the render phase. Continue the
1029
+ // loop to handle the new error.
1030
+ continue;
1031
}
1032
+
1033
+ // We're finished working. Exit the error loop.
1034
+ break;
1035
}
1036
1037
+ // Reset the priority context to its previous value.
1038
+ priorityContext = previousPriorityContext;
1039
+
1040
// If we're inside a callback, set this to false, since we just flushed it.
1041
if (deadline !== null) {
1042
isCallbackScheduled = false;
1043
}
1044
// If there's remaining async work, make sure we schedule another callback.
997
- if (hasRemainingAsyncWork && !isCallbackScheduled) {
1045
+ if (nextPriorityLevel > TaskPriority && !isCallbackScheduled) {
1046
scheduleDeferredCallback(performDeferredWork);
1047
isCallbackScheduled = true;
1048
}
1049
1002
- const errorToThrow = fatalError !== null ? fatalError : firstUncaughtError;
1050
+ const errorToThrow = firstUncaughtError;
1051
1052
// We're done performing work. Time to clean up.
1053
isPerformingWork = false;
1054
deadlineHasExpired = false;
1007
- fatalError = null;
1055
+ didFatal = false;
1056
firstUncaughtError = null;
1057
capturedErrors = null;
1058
failedBoundaries = null;
1011
- nestedSyncUpdates = 0;
1059
if (__DEV__) {
1060
stopWorkLoopTimer();
1061
}
1073
if (__DEV__) {
1074
ReactDebugCurrentFiber.resetCurrentFiber();
1075
}
1029
- // It is no longer valid because this unit of work failed.
1030
- nextUnitOfWork = null;
1076
1077
// Search for the nearest error boundary.
1078
let boundary: Fiber | null = null;
1092
// If this root already failed, there must have been an error when
1093
// attempting to unmount it. This is a worst-case scenario and
1094
// should only be possible if there's a bug in the renderer.
1050
- fatalError = error;
1095
+ didFatal = true;
1096
}
1097
} else {
1098
let node = failedWork.return;
1186
commitPhaseBoundaries.add(boundary);
1187
} else {
1188
// Otherwise, schedule an update now.
1189
+ // TODO: Is this actually necessary during the render phase? Is it
1190
+ // possible to unwind and continue rendering at the same priority,
1191
+ // without corrupting internal state?
1192
scheduleErrorRecovery(boundary);
1193
}
1194
return boundary;
1279
1280
function unwindContexts(from: Fiber, to: Fiber) {
1281
let node = from;
1234
- while (node !== null && node !== to && node.alternate !== to) {
1282
+ while (node !== null) {
1283
switch (node.tag) {
1284
case ClassComponent:
1285
popContextProvider(node);
1294
popHostContainer(node);
1295
break;
1296
}
1249
- if (__DEV__) {
1297
+ if (node === to || node.alternate === to) {
1298
+ if (__DEV__) {
1299
+ stopFailedWorkTimer(node);
1300
+ }
1301
+ break;
1302
+ } else if (__DEV__) {
1303
stopWorkTimer(node);
1304
}
1305
node = node.return;
1330
recordScheduleUpdate();
1331
}
1332
1280
- if (priorityLevel <= nextPriorityLevel) {
1333
+ if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
1334
+ didFatal = true;
1335
+ invariant(
1336
+ false,
1337
+ 'Maximum update depth exceeded. This can happen when a ' +
1338
+ 'component repeatedly calls setState inside componentWillUpdate or ' +
1339
+ 'componentDidUpdate. React limits the number of nested updates to ' +
1340
+ 'prevent infinite loops.',
1341
+ );
1342
+ }
1343
+
1344
+ if (!isPerformingWork && priorityLevel <= nextPriorityLevel) {
1345
// We must reset the current unit of work pointer so that we restart the
1346
// search from the root during the next tick, in case there is now higher
1347
// priority work somewhere earlier than before.