751
let isFlushingPassiveEffects = false;
752
let didScheduleUpdateDuringPassiveEffects = false;
753
754
+const NO_NESTED_UPDATE = 0;
755
+const NESTED_UPDATE_SYNC_LANE = 1;
756
+const NESTED_UPDATE_PHASE_SPAWN = 2;
757
+let nestedUpdateKind: 0 | 1 | 2 = NO_NESTED_UPDATE;
758
+
759
const NESTED_PASSIVE_UPDATE_LIMIT = 50;
760
let nestedPassiveUpdateCount: number = 0;
761
let rootWithPassiveNestedUpdates: FiberRoot | null = null;
4318
// hydration lanes in this check, because render triggered by selective
4319
// hydration is conceptually not an update.
4320
if (
4321
+ // Was the finished render the result of an update (not hydration)?
4322
+ includesSomeLane(lanes, UpdateLanes) &&
4323
+ // Did it schedule a sync update?
4324
+ includesSomeLane(remainingLanes, SyncUpdateLanes)
4325
+ ) {
4326
+ if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
4327
+ markNestedUpdateScheduled();
4328
+ }
4329
+
4330
+ // Count the number of times the root synchronously re-renders without
4331
+ // finishing. If there are too many, it indicates an infinite update loop.
4332
+ if (root === rootWithNestedUpdates) {
4333
+ nestedUpdateCount++;
4334
+ } else {
4335
+ nestedUpdateCount = 0;
4336
+ rootWithNestedUpdates = root;
4337
+ }
4338
+ nestedUpdateKind = NESTED_UPDATE_SYNC_LANE;
4339
+ } else if (
4340
// Check if there was a recursive update spawned by this render, in either
4341
// the render phase or the commit phase. We track these explicitly because
4342
// we can't infer from the remaining lanes alone.
4319
- (enableInfiniteRenderLoopDetection &&
4320
- (didIncludeRenderPhaseUpdate || didIncludeCommitPhaseUpdate)) ||
4321
- // Was the finished render the result of an update (not hydration)?
4322
- (includesSomeLane(lanes, UpdateLanes) &&
4323
- // Did it schedule a sync update?
4324
- includesSomeLane(remainingLanes, SyncUpdateLanes))
4343
+ enableInfiniteRenderLoopDetection &&
4344
+ (didIncludeRenderPhaseUpdate || didIncludeCommitPhaseUpdate)
4345
) {
4346
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
4347
markNestedUpdateScheduled();
4355
nestedUpdateCount = 0;
4356
rootWithNestedUpdates = root;
4357
}
4358
+ nestedUpdateKind = NESTED_UPDATE_PHASE_SPAWN;
4359
} else {
4360
nestedUpdateCount = 0;
4361
+ rootWithNestedUpdates = null;
4362
+ nestedUpdateKind = NO_NESTED_UPDATE;
4363
}
4364
4365
if (enableProfilerTimer && enableComponentPerformanceTrack) {
5175
rootWithNestedUpdates = null;
5176
rootWithPassiveNestedUpdates = null;
5177
5178
+ const updateKind = nestedUpdateKind;
5179
+ nestedUpdateKind = NO_NESTED_UPDATE;
5180
+
5181
if (enableInfiniteRenderLoopDetection) {
5156
- if (executionContext & RenderContext && workInProgressRoot !== null) {
5157
- // We're in the render phase. Disable the concurrent error recovery
5158
- // mechanism to ensure that the error we're about to throw gets handled.
5159
- // We need it to trigger the nearest error boundary so that the infinite
5160
- // update loop is broken.
5161
- workInProgressRoot.errorRecoveryDisabledLanes = mergeLanes(
5162
- workInProgressRoot.errorRecoveryDisabledLanes,
5163
- workInProgressRootRenderLanes,
5164
- );
5182
+ if (updateKind === NESTED_UPDATE_SYNC_LANE) {
5183
+ if (executionContext & RenderContext && workInProgressRoot !== null) {
5184
+ // This loop was identified only because of the instrumentation gated with enableInfiniteRenderLoopDetection, warn instead of throwing.
5185
+ if (__DEV__) {
5186
+ console.error(
5187
+ 'Maximum update depth exceeded. This could be an infinite loop. This can happen when a component ' +
5188
+ 'repeatedly calls setState during render phase or inside useLayoutEffect, ' +
5189
+ 'causing infinite render loop. React limits the number of nested updates to ' +
5190
+ 'prevent infinite loops.',
5191
+ );
5192
+ }
5193
+ } else {
5194
+ throw new Error(
5195
+ 'Maximum update depth exceeded. This can happen when a component ' +
5196
+ 'repeatedly calls setState inside componentWillUpdate or ' +
5197
+ 'componentDidUpdate. React limits the number of nested updates to ' +
5198
+ 'prevent infinite loops.',
5199
+ );
5200
+ }
5201
+ } else if (updateKind === NESTED_UPDATE_PHASE_SPAWN) {
5202
+ if (__DEV__) {
5203
+ console.error(
5204
+ 'Maximum update depth exceeded. This could be an infinite loop. This can happen when a component ' +
5205
+ 'repeatedly calls setState during render phase or inside useLayoutEffect, ' +
5206
+ 'causing infinite render loop. React limits the number of nested updates to ' +
5207
+ 'prevent infinite loops.',
5208
+ );
5209
+ }
5210
}
5211
+ } else {
5212
+ throw new Error(
5213
+ 'Maximum update depth exceeded. This can happen when a component ' +
5214
+ 'repeatedly calls setState inside componentWillUpdate or ' +
5215
+ 'componentDidUpdate. React limits the number of nested updates to ' +
5216
+ 'prevent infinite loops.',
5217
+ );
5218
}
5167
-
5168
- throw new Error(
5169
- 'Maximum update depth exceeded. This can happen when a component ' +
5170
- 'repeatedly calls setState inside componentWillUpdate or ' +
5171
- 'componentDidUpdate. React limits the number of nested updates to ' +
5172
- 'prevent infinite loops.',
5173
- );
5219
}
5220
5221
if (__DEV__) {