facebook/react · error

598

598

Error message

Maximum update depth exceeded. This could be an infinite loop. This can happen when a component repeatedly calls setState during render phase or inside useLayoutEffect, causing infinite render loop. React limits the number of nested updates to prevent infinite loops.

What it means

React counts nested updates per root and caps them at NESTED_UPDATE_LIMIT (50). When the infinite-render-loop instrumentation (enableInfiniteRenderLoopDetectionForceThrow) determines the newest update in the chain was scheduled from render phase or useLayoutEffect, throwForcedInfiniteRenderLoopError disables error-recovery lanes for that render (so the throw is not silently retried) and throws this variant naming render-phase setState and useLayoutEffect as the cause.

Source

Thrown at packages/react-reconciler/src/ReactFiberWorkLoop.js:5214

  }

  retryTimedOutBoundary(boundaryFiber, retryLane);
}

function throwForcedInfiniteRenderLoopError(
  root: FiberRoot | null,
  renderLanes: Lanes,
): empty {
  if (root !== null) {
    // Disable concurrent error recovery for the in-progress render so the thrown
    // error reaches the nearest error boundary and breaks the infinite update
    // loop instead of being silently retried by the recovery mechanism.
    root.errorRecoveryDisabledLanes = mergeLanes(
      root.errorRecoveryDisabledLanes,
      renderLanes,
    );
  }
  throw new Error(
    'Maximum update depth exceeded. This could be an infinite loop. This can happen when a component ' +
      'repeatedly calls setState during render phase or inside useLayoutEffect, ' +
      'causing infinite render loop. React limits the number of nested updates to ' +
      'prevent infinite loops.',
  );
}

export function throwIfInfiniteUpdateLoopDetected(
  isFromInfiniteRenderLoopDetectionInstrumentation: boolean,
) {
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    nestedUpdateCount = 0;
    nestedPassiveUpdateCount = 0;
    rootWithNestedUpdates = null;
    rootWithPassiveNestedUpdates = null;

    const updateKind = nestedUpdateKind;
    nestedUpdateKind = NO_NESTED_UPDATE;

View on GitHub (pinned to eafeac097b)

Solutions

  1. Derive the value during render instead of storing it, or guard the update: only setState when the value actually changed
  2. If state must be set during render, follow React's documented pattern: set the same component's state and bail out when unchanged
  3. Move non-layout synchronization from useLayoutEffect to useEffect with a correct dependency array
  4. Lift the state to the closest common owner so one update replaces the ping-pong between components

Example fix

// before
function List({items}) {
  const [selected, setSelected] = useState([]);
  useLayoutEffect(() => {
    setSelected(items.map((i) => i.id)); // runs after every commit -> infinite loop
  });
  return <Rows selected={selected} items={items} />;
}

// after
function List({items}) {
  const selected = items.map((i) => i.id); // derived during render, no state
  return <Rows selected={selected} items={items} />;
}
Defensive patterns

Strategy: validation

Validate before calling

// guard effect-driven updates so identical values bail out
useLayoutEffect(() => {
  setViewport((prev) =>
    prev.width === box.width && prev.height === box.height ? prev : box,
  );
}, [box]);

Try / catch

// the throw is catchable by an error boundary (error recovery is disabled for these lanes)
class LoopBoundary extends React.Component {
  state = {error: null};
  static getDerivedStateFromError(error) { return {error}; }
  componentDidCatch(error) {
    if (error.message.includes('Maximum update depth exceeded')) {
      logInfiniteLoop(this.props.name);
    }
  }
  render() { return this.state.error ? null : this.props.children; }
}

Prevention

When it happens

Trigger: More than 50 nested updates where the last update came from render phase or a layout effect: setState called unconditionally during render, or a useLayoutEffect that sets state after every commit without an equality guard or with missing dependencies, so each render schedules another render.

Common situations: Syncing props into state with an effect that always writes because of fresh object/array identity; 'adjusting state during render' done without an equality check; scroll/measure loops in useLayoutEffect; store subscriptions writing back on every notification.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/0fa7f5f0bb662fc5. Report an issue: GitHub.