Tutorials Logic, IN info@tutorialslogic.com

React Maximum Update Depth Exceeded: Infinite Update Loops and Dependency Fixes

What the Error Means

Maximum update depth exceeded means React is being driven through a post-render feedback loop. An effect, layout effect, or callback writes state, the commit happens again, and the same trigger comes back with a fresh render snapshot.

This is different from a setter call during render. The loop happens after the DOM update, usually because the dependency list keeps changing identity or because the effect writes state that it also depends on.

The clean fix is usually to remove mirrored state, stabilise the dependency value, or only write when the next value is actually different from the current one.

Maximum update depth exceeded means React detected an update loop. A state update triggers render, render or effect triggers another state update, and the cycle repeats until React stops it.

The page should always teach dependency identity. Functions, arrays, and objects created during render are new references on every render, so effects that depend on them can run repeatedly even when their visible contents look unchanged.

React finishes rendering, commits the DOM, and then runs effects. If one of those effects writes state, React schedules another render. If the effect depends on a value that keeps changing identity, the same effect runs again and writes state again. The cycle keeps repeating until React aborts it.

The defining behavior is feedback, not speed. The component is not merely rendering often; it is feeding its own output back into another render.

  • The loop starts after commit, not during JSX evaluation.
  • A changing dependency restarts the effect even when the visible data looks the same.
  • The error is React stopping an unbounded update cycle.

What Usually Triggers It

A common trigger is an effect that fetches or derives data, then writes that result into state while depending on an object, array, or callback that is recreated on every render. Another version is a child effect that writes back into parent state, which recreates the child props and restarts the same path.

A more subtle version appears when a component keeps a second copy of a value that already exists in props. The effect mirrors props into state, the render sees a new state object, and the cycle continues.

  • Fresh objects or arrays in dependency arrays.
  • Effects that write the same state they watch.
  • Parent-child state ping-pong.
  • Copied props that do not need their own local state.

How to Stop the Loop

Use primitives in dependency arrays whenever the effect only needs a small scalar value such as an id or status. If a structured value is necessary, memoize it so React sees the same reference until one of the real inputs changes.

When the update depends on the previous value, use the functional updater form. When the value can be calculated directly from props or other state, delete the mirrored copy and compute it inline instead.

  • Prefer primitive dependencies over recreated objects.
  • Use useMemo for object dependencies and useCallback for function dependencies.
  • Compare old and new values before calling a setter.
  • Delete duplicated state that only mirrors another source of truth.

Looping effect and fixed version

Looping effect and fixed version
function QueueBanner({ ticket }) {\n  const [label, setLabel] = useState("");\n  const options = { ticketId: ticket.id };\n\n  // Wrong options is recreated every render\n  useEffect(() => {\n    setLabel(ticket.priority + " queue");\n  }, [options, label]);\n\n  return <p>{label}</p>;\n}\n\nfunction QueueBannerFixed({ ticket }) {\n  const [label, setLabel] = useState("");\n  const options = useMemo(() => ({ ticketId: ticket.id }), [ticket.id]);\n\n  useEffect(() => {\n    const nextLabel = ticket.priority + " queue";\n    setLabel((current) => (current === nextLabel ? current : nextLabel));\n  }, [ticket.priority, options]);\n\n  return <p>{label}</p>;\n}

How the Cycle Repeats

On each pass, React creates a fresh render snapshot. After commit, the effect sees that snapshot and decides whether to write state. If the dependency identity keeps changing, the effect has no reason to settle, so React keeps scheduling more renders.

That is why a loop can feel invisible in the code. Each individual line looks fine, but the combination of "new dependency every render" plus "state write in the effect" creates a machine that never stabilises.

  • Render creates snapshot A.
  • Commit applies snapshot A to the DOM.
  • Effect writes state and produces snapshot B.
  • A changing dependency makes the effect run again.

Debugging the Loop

Log inside the effect and inside the component body. If the effect fires repeatedly without user action, inspect the dependency list. If the effect only begins after a prop change, check whether that prop is actually a fresh object or callback on every render.

If the state being written is just a transformed copy of something already available, remove the copy. The cleanest fix is often to delete the local state rather than trying to keep it in sync.

  • Inspect the exact values in the dependency array.
  • Check object and function identity, not just deep content.
  • Look for state that mirrors props or derived data.
  • Use React DevTools Profiler to see which component keeps repeating.

Best Practices

  • Keep dependencies as small and stable as possible.
  • Only write state when the next value is actually different.
  • Prefer derived values over duplicated state.
  • Memoize values that must keep identity across renders.
  • Treat parent-child feedback paths with extra care.

Effect Dependency Loops

The most common cause is a useEffect that updates state and depends on a value that changes because of that update. Another common cause is leaving out the dependency array, causing the effect to run after every render.

The dependency array should describe the values used by the effect. If adding a dependency creates a loop, the problem may be unstable references, mirrored state, or logic that should be handled in an event instead of an effect.

  • Use [] only for true mount-only effects.
  • Include real dependencies used inside the effect.
  • Avoid setting state to the same derived value repeatedly.
  • Stabilize callbacks with useCallback only when needed.

Unstable Reference Notes

Every render creates new object, array, and function literals. If an effect depends on one of those values, React sees it as changed every time. That can rerun the effect and cause another state update.

Move constants outside the component, compute simple values directly, or use useMemo/useCallback for values that genuinely need stable identity. Do not memoize everything; use it when identity is part of the bug or performance need.

  • Do not put fresh object literals in dependency arrays.
  • Prefer primitive dependencies when possible.
  • Use functional state updates to avoid stale state loops.
  • Use React DevTools Profiler to find repeated renders.

Wrong: Effect Runs Forever

Wrong: Effect Runs Forever
function SearchPage() {
  const [filters, setFilters] = useState({});
  const defaultFilters = { active: true };

  useEffect(() => {
    setFilters(defaultFilters);
  }, [defaultFilters]); // new object every render

  return <pre>{JSON.stringify(filters)}</pre>;
}

Correct: Stable Default Value

Correct: Stable Default Value
const defaultFilters = { active: true };

function SearchPage() {
  const [filters, setFilters] = useState(defaultFilters);

  useEffect(() => {
    setFilters(defaultFilters);
  }, []);

  return <pre>{JSON.stringify(filters)}</pre>;
}
Before you move on

React Maximum Update Depth Exceeded: Infinite Update Loops and Dependency Fixes Mastery Check

5 checks
  • Find which state update repeats.
  • Check useEffect blocks that call setters.
  • Inspect dependency arrays for objects, arrays, and inline functions.
  • Use functional updates when new state depends on previous state.
  • Remove effects that only calculate derived values.

Try this next

React JS Maximum Update Depth Repair Drills

0 of 2 completed

  1. Reproduce an effect that updates one of its dependencies, then derive the value during render or use a guarded transition and compare render counts. Write down which dependency changes after the setter before changing the dependency array.
  2. Trace an inline object or function that retriggers an effect, move it inside the effect or memoize it only when identity is part of the contract. Removing a dependency can hide stale data; remove the unnecessary effect instead when possible.

React JS Questions Learners Ask

React throws this when updates keep triggering more updates after render. The classic version is an effect that sets state, then runs again because that same state or an unstable dependency changed.

"Too many re-renders" usually means the setter ran during render before React could finish the current pass. "Maximum update depth exceeded" usually means React committed a render, then an effect or lifecycle-style update pushed it into another render repeatedly. That difference tells you where to inspect.

React compares dependencies with reference equality. A new object literal such as { page, sort } is a different reference on every render even when the fields are the same. If an effect depends on that object and sets state, each render creates a new dependency, the effect runs again, and the loop continues.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.