Skip to content

Commit def5042

Browse files
committed
Disable error recovery mechanism if infinite loop is detected
If an infinite update loop is caused by a render phase update, the mechanism we typically use to break the loop doesn't work. Our mechanism assumes that by throwing inside `setState`, the error will caise the component to be unmounted, but that only works if the error happens in an effect or lifecycle method. During the render phase, what happens is that React will try to render the component one more time, synchronously, which we do as a way to recover from concurrent data races. But during this second attempt, the "Maximum update" error won't be thrown, because the counter was already reset. I considered a few different ways to fix this, like waiting to reset the counter until after the error has been surfaced. However, it's not obvious where this should happen. So instead the approach I landed on is to temporarily disable the error recovery mechanism. This is the same trick we use to prevent infinite ping loops when an uncached promise is passed to `use` during a sync render. This category of error is also covered by the more generic loop guard I added in the previous commit, but I also confirmed that this change alone fixes it.
1 parent 43bfd09 commit def5042

File tree

1 file changed

+11
-0
lines changed

1 file changed

+11
-0
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3463,6 +3463,17 @@ export function throwIfInfiniteUpdateLoopDetected() {
34633463
rootWithNestedUpdates = null;
34643464
rootWithPassiveNestedUpdates = null;
34653465

3466+
if (executionContext & RenderContext && workInProgressRoot !== null) {
3467+
// We're in the render phase. Disable the concurrent error recovery
3468+
// mechanism to ensure that the error we're about to throw gets handled.
3469+
// We need it to trigger the nearest error boundary so that the infinite
3470+
// update loop is broken.
3471+
workInProgressRoot.errorRecoveryDisabledLanes = mergeLanes(
3472+
workInProgressRoot.errorRecoveryDisabledLanes,
3473+
workInProgressRootRenderLanes,
3474+
);
3475+
}
3476+
34663477
throw new Error(
34673478
'Maximum update depth exceeded. This can happen when a component ' +
34683479
'repeatedly calls setState inside componentWillUpdate or ' +

0 commit comments

Comments
 (0)