React 'Maximum Update Depth Exceeded' Error: Fix Guide

'Too many re-renders' and 'Maximum update depth exceeded' mean an infinite render loop. The 5 patterns that cause it, with before and after code for each.

"Maximum update depth exceeded" and "Too many re-renders" mean your component keeps updating state during or immediately after every render, creating an infinite loop; the cause is almost always a state update in the render body, a useEffect that changes its own dependency, or an event handler that is called instead of passed.

React stops the loop after a fixed number of nested updates to protect the tab from freezing. In production the page crashes or goes blank, which makes it a real user-facing failure.

Pattern 1: Calling the Handler Instead of Passing It

// Wrong: runs during render, sets state, re-renders, repeats
<button onClick={setCount(count + 1)}>Add</button>

// Right: pass a function
<button onClick={() => setCount(count + 1)}>Add</button>

The same mistake happens with onClick={handleClick(id)}. Write onClick={() => handleClick(id)}.

Pattern 2: Setting State in the Render Body

function Profile({ user }) {
  const [name, setName] = useState('')
  setName(user.name) // wrong: update on every render
  return <p>{name}</p>
}

Derive the value instead. If it can be computed from props, you do not need state:

const name = user.name

Pattern 3: useEffect Updates Its Own Dependency

useEffect(() => {
  setItems([...items, newItem]) // changes items, which re-triggers the effect
}, [items])

Use the functional updater and a narrower dependency list:

useEffect(() => {
  setItems(prev => [...prev, newItem])
}, [newItem])

Pattern 4: New Object or Array in Dependencies

const options = { page, sort }            // new object every render
useEffect(() => { load(options) }, [options]) // runs every render

Depend on primitives, or memoize:

useEffect(() => { load({ page, sort }) }, [page, sort])

The same trap exists with inline functions passed to effects. Wrap them in useCallback or define them inside the effect.

Pattern 5: Two Components Update Each Other

Parent sets state from a child's callback, and the child calls that callback in an effect that depends on a value the parent just changed. Break the cycle by lifting state to one owner and passing it down, or by comparing before setting:

setValue(prev => (prev === next ? prev : next))

React skips re-rendering when the new state is identical (Object.is), so returning prev stops the loop.

Find It Fast

Clue Likely pattern
Error appears on mount 1 or 2
Error appears after an action, with effects nearby 3 or 4
Two components in the stack trace 5
React DevTools Profiler shows constant renders Any

Use React DevTools' "Highlight updates when components render", and add a temporary console.count('render') in the suspect component. The ESLint rule react-hooks/exhaustive-deps catches many of these; do not silence it without understanding why.

Contain the Damage in Production

An infinite loop in one widget should not blank the whole page. Wrap risky sections in an error boundary so the rest of the app survives; see React Error Boundaries in Production. Hydration bugs can also cause re-render storms; compare with Next.js hydration mismatch. If the error message is instead about undefined, see TypeError: Cannot read properties of undefined.

Let Relia Report What Users Hit

Render loops often appear only with real data shapes. Relia captures the user's session (clicks, inputs, navigation, errors) when a page crashes and writes the incident up in plain words: what happened, why it thinks so (with a confidence level), and one thing to do next. On request it writes a fix plan for the effect or handler with a "Copy for your AI assistant" button; Relia doesn't change code itself.

FAQ

What causes "Too many re-renders" in React?

State is being set during render, so React renders again, sets state again, and never settles.

Is it safe to remove a dependency from useEffect to stop the loop?

Only if the effect truly does not use it. Otherwise you trade a loop for stale data. Use functional updates or memoization instead.

Why does it only break with production data?

Real data produces new object references or values that differ every render, which triggers effects that never did with mock data.

All guides