The maximum update depth exceeded error means a state update keeps triggering another update forever. React stops the loop and throws so the page does not freeze.
In current React the same bug often appears as "Too many re-renders. React limits the number of renders to prevent an infinite loop."
What the error means
React limits nested updates to prevent infinite loops. When a setter runs during render, or inside an Effect that fires after every render, each render schedules the next one until React gives up and throws. The limit protects the browser from an endless stream of work, and the loop underneath is still a real bug to fix.
The stack trace points at the setter that started the loop, which is the fastest way to find the culprit. Strict Mode does not cause this error, but it makes a render-time side effect appear sooner in development. Class components hit the same wall through setState inside componentDidUpdate without a guard.
Setting state during render
The clearest case is a setter called at the top level of the component body.
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1);
return <p>Count: {count}</p>;
}setCount runs on every render, which schedules another render, which runs setCount again. The fix is to move the update into an event handler so it only runs when the user acts.
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}Now the update happens once per click, and the loop is gone. The setter still lives in the component, but it only fires inside a handler, which is where user actions belong.
An Effect that fires every render
An Effect without a dependency array runs after every render, so setting state inside it loops too.
import { useEffect, useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});
return <p>Count: {count}</p>;
}The Effect sets state, which triggers a render, which runs the Effect again. The fix is usually to remove the Effect, or to give it the dependency it actually needs.
Setting state after mount is rarely the right way to derive data. See you might not need an Effect for the alternatives.
The event handler mistake
A handler that runs during render looks almost the same as one that runs on click.
<button onClick={handleClick()}>Click me</button>The parentheses call handleClick during render, so any state update inside it loops forever. Pass the function itself instead, and React calls it only when the user clicks the button.
<button onClick={handleClick}>Click me</button>Verify the fix
Reload and interact with the component. The console should stay clean and the counter should move once per click, which is the proof the loop is gone. How to debug React applications systematically walks through isolating the offending setter when the stack is unclear.
Rune AI
Key Insights
- The error means a state update triggers itself forever.
- Move render-time updates into event handlers.
- Give Effects a correct dependency array.
- Pass handlers to onClick, never call them during render.
- Strict Mode surfaces the loop faster in development.
Frequently Asked Questions
Is this error the same as too many re-renders?
Where do I start looking?
Conclusion
Maximum update depth exceeded means a state update loops forever. Remove render-time updates, fix Effect dependencies, and pass event handlers instead of calling them during render.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.