diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js index ee35a11640e2f..74e19f40b4982 100644 --- a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js +++ b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js @@ -19,7 +19,10 @@ import type { import type {Lane, Lanes} from './ReactFiberLane.new'; import type {OffscreenInstance} from './ReactFiberOffscreenComponent'; -import {warnAboutUpdateOnNotYetMountedFiberInDEV} from './ReactFiberWorkLoop.new'; +import { + warnAboutUpdateOnNotYetMountedFiberInDEV, + throwIfInfiniteUpdateLoopDetected, +} from './ReactFiberWorkLoop.new'; import { NoLane, NoLanes, @@ -207,6 +210,13 @@ function markUpdateLaneFromFiberToRoot( } function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null { + // TODO: We will detect and infinite update loop and throw even if this fiber + // has already unmounted. This isn't really necessary but it happens to be the + // current behavior we've used for several release cycles. Consider not + // performing this check if the updated fiber already unmounted, since it's + // not possible for that to cause an infinite update loop. + throwIfInfiniteUpdateLoopDetected(); + // When a setState happens, we must ensure the root is scheduled. Because // update queues do not have a backpointer to the root, the only way to do // this currently is to walk up the return path. This used to not be a big diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js index 8f3a11c938f65..42b6520238c15 100644 --- a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js +++ b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js @@ -18,7 +18,10 @@ import type { } from './ReactFiberClassUpdateQueue.old'; import type {Lane} from './ReactFiberLane.old'; -import {warnAboutUpdateOnNotYetMountedFiberInDEV} from './ReactFiberWorkLoop.old'; +import { + warnAboutUpdateOnNotYetMountedFiberInDEV, + throwIfInfiniteUpdateLoopDetected, +} from './ReactFiberWorkLoop.old'; import {mergeLanes} from './ReactFiberLane.old'; import {NoFlags, Placement, Hydrating} from './ReactFiberFlags'; import {HostRoot} from './ReactWorkTags'; @@ -143,6 +146,13 @@ function markUpdateLaneFromFiberToRoot( sourceFiber: Fiber, lane: Lane, ): FiberRoot | null { + // TODO: We will detect and infinite update loop and throw even if this fiber + // has already unmounted. This isn't really necessary but it happens to be the + // current behavior we've used for several release cycles. Consider not + // performing this check if the updated fiber already unmounted, since it's + // not possible for that to cause an infinite update loop. + throwIfInfiniteUpdateLoopDetected(); + // Update the source fiber's lanes sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); let alternate = sourceFiber.alternate; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index e7e95b2694e07..21f9d17850f1c 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -536,8 +536,6 @@ export function scheduleUpdateOnFiber( lane: Lane, eventTime: number, ) { - checkForNestedUpdates(); - if (__DEV__) { if (isRunningInsertionEffect) { console.error('useInsertionEffect must not schedule updates.'); @@ -2782,10 +2780,12 @@ function jnd(timeElapsed: number) { : ceil(timeElapsed / 1960) * 1960; } -function checkForNestedUpdates() { +export function throwIfInfiniteUpdateLoopDetected() { if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { nestedUpdateCount = 0; + nestedPassiveUpdateCount = 0; rootWithNestedUpdates = null; + rootWithPassiveNestedUpdates = null; throw new Error( 'Maximum update depth exceeded. This can happen when a component ' + diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index dc517877393c9..4f0af4387098e 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -531,8 +531,6 @@ export function scheduleUpdateOnFiber( lane: Lane, eventTime: number, ) { - checkForNestedUpdates(); - if (__DEV__) { if (isRunningInsertionEffect) { console.error('useInsertionEffect must not schedule updates.'); @@ -2771,7 +2769,7 @@ function jnd(timeElapsed: number) { : ceil(timeElapsed / 1960) * 1960; } -function checkForNestedUpdates() { +export function throwIfInfiniteUpdateLoopDetected() { if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { nestedUpdateCount = 0; rootWithNestedUpdates = null; diff --git a/scripts/merge-fork/forked-revisions b/scripts/merge-fork/forked-revisions index 517ed44977fe4..c4bdf3a85b0e2 100644 --- a/scripts/merge-fork/forked-revisions +++ b/scripts/merge-fork/forked-revisions @@ -1,2 +1,3 @@ +58bb11764bf0bb6db47527a64f693f67cdd3b0bb [FORKED] Check for infinite update loops even if unmounted 31882b5dd66f34f70d341ea2781cacbe802bf4d5 [FORKED] Bugfix: Revealing a hidden update 17691acc071d56261d43c3cf183f287d983baa9b [FORKED] Don't update childLanes until after current render