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 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.
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.
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.
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}
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.
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.
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.
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.
function SearchPage() {
const [filters, setFilters] = useState({});
const defaultFilters = { active: true };
useEffect(() => {
setFilters(defaultFilters);
}, [defaultFilters]); // new object every render
return <pre>{JSON.stringify(filters)}</pre>;
}
const defaultFilters = { active: true };
function SearchPage() {
const [filters, setFilters] = useState(defaultFilters);
useEffect(() => {
setFilters(defaultFilters);
}, []);
return <pre>{JSON.stringify(filters)}</pre>;
}
Removing all dependencies to silence the loop.
Fix the cause of unstable or unnecessary updates.
Creating objects inside render and depending on them.
Move stable constants outside or memoize intentionally.
Using effects for every calculation.
Calculate derived values during render when no side effect is needed.
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.
Explore 500+ free tutorials across 20+ languages and frameworks.