Tutorials Logic, IN info@tutorialslogic.com

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

React Maximum Update Depth Exceeded

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.

What the Error Means

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 useful mental model is feedback, not speed. The component is not merely rendering a lot; it is being fed its own output in a loop.

  • 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  // ❌ 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>;
}
Key Takeaways
  • 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.
Common Mistakes to Avoid
WRONG Removing all dependencies to silence the loop.
RIGHT Fix the cause of unstable or unnecessary updates.
Missing dependencies can create stale data bugs.
WRONG Creating objects inside render and depending on them.
RIGHT Move stable constants outside or memoize intentionally.
Object identity changes every render.
WRONG Using effects for every calculation.
RIGHT Calculate derived values during render when no side effect is needed.
Effects are for synchronization, not normal math.

Practice Tasks

  • Create an infinite effect loop and fix it with stable dependencies.
  • Replace mirrored state with a derived const value.
  • Use a functional state update to increment safely.
  • Profile a repeated render and identify the component causing it.

Frequently Asked Questions

This error occurs when useEffect triggers a state update that causes a re-render, which triggers the effect again, creating an infinite loop. Common causes: no dependency array, objects in deps, or state that depends on itself.

"Too many re-renders" usually means setState is called during the render phase. "Maximum update depth exceeded" means useEffect is causing an infinite update cycle after rendering.

Objects are compared by reference in JavaScript. A new object {} !== {} even with identical content. So React sees the dependency as changed on every render, re-running the effect endlessly.

Instead of setCount(count + 1), use setCount(prev => prev + 1). This reads the latest state without needing count in the dependency array, breaking the loop.

Add console.log inside the effect to count how many times it runs. Check the dependency array for objects/arrays. Use React DevTools Profiler to see which component is re-rendering excessively.

Ready to Level Up Your Skills?

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