The Error
Too many re-renders. React limits the number of renders to prevent an infinite loop.
React bails out after hitting a render limit โ roughly 25 cycles in development. Your app freezes, the console floods with the same error, and sometimes the browser tab crashes entirely. It almost always means a component is scheduling its own re-render on every paint.
Root Cause
Three patterns cause almost every case:
- Calling a state setter directly in the render body โ not inside a handler or effect
- Passing an invoked function to an event prop instead of a function reference
- A
useEffectwhose dependency changes on every render, creating a loop
Fix 1: State setter called during render
The most common culprit. The setter fires, triggers a re-render, the setter fires again. React kills it at ~25 iterations before the browser dies.
// โ Wrong โ setCount runs on every render
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // direct call in render body
return <div>{count}</div>;
}
// โ
Fix โ move it into useEffect or an event handler
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // runs after mount, not during render
}, []); // empty deps = once only
return <div>{count}</div>;
}
Fix 2: Calling the function instead of passing it
One extra pair of parentheses causes this. onClick={handleClick()} calls the function immediately during render. onClick={handleClick} passes it as a reference for React to call later, on click.
// โ Wrong โ handleClick() executes during render, not on click
function MyButton() {
const [open, setOpen] = useState(false);
function handleClick() {
setOpen(true);
}
return <button onClick={handleClick()}>Open</button>;
}
// โ
Fix โ pass the reference, no parentheses
return <button onClick={handleClick}>Open</button>;
Need to pass arguments? Wrap it in an arrow function instead:
// โ
Arrow function โ safe, does not invoke on render
return <button onClick={() => handleClick(someId)}>Open</button>;
Fix 3: useEffect dependency loop
An effect that modifies one of its own dependencies runs forever. State changes โ effect re-runs โ state changes again. Breaking the cycle means removing the state variable from the dependency array.
// โ Wrong โ items changes โ effect runs โ items changes โ loop
useEffect(() => {
setItems([...items, newItem]);
}, [items]);
// โ
Fix โ functional updater reads latest state without needing it in deps
useEffect(() => {
setItems(prev => [...prev, newItem]);
}, [newItem]); // only re-runs when newItem changes
Fix 4: Inline object or array as a dependency
This one is subtle. JavaScript creates a brand-new object reference on every render, even when the values inside are identical. React uses reference equality for dependency checks โ so { id: 1 } written inline is always "changed".
// โ Wrong โ { id: 1 } is a new reference on every render
useEffect(() => {
fetchData({ id: 1 });
}, [{ id: 1 }]);
// โ
Fix โ stabilize with useMemo so the reference stays the same between renders
const params = useMemo(() => ({ id: 1 }), []);
useEffect(() => {
fetchData(params);
}, [params]);
Fix 5: Conditional setState that never stops
Setting state inside an if block looks harmless โ but when the condition is always true, it fires on every single render.
// โ Wrong โ condition is always true while data exists
function Form({ data }) {
const [value, setValue] = useState('');
if (data) {
setValue(data.name); // runs every render as long as data is truthy
}
return <input value={value} />;
}
// โ
Fix โ move the sync into useEffect with data as the dependency
function Form({ data }) {
const [value, setValue] = useState('');
useEffect(() => {
if (data) {
setValue(data.name);
}
}, [data]); // only runs when data actually changes
return <input value={value} />;
}
How to Find Which Component Is Looping
React's stack trace often points at internal fiber code, not your file. Use these steps to pinpoint the actual offender:
- Open React DevTools โ Profiler tab โ hit Record โ your looping component will show 50โ200 renders in under a second
- Drop
console.count('MyComponent')at the top of each suspect โ the one printing 100+ is your culprit - Comment out
useEffecthooks one by one until the loop stops โ that's the one causing it - Scan every event prop (
onClick,onChange,onSubmit) for accidental()after the handler name
Verify the Fix
- Error disappears from the console
- React DevTools Profiler shows the component rendering only on user interaction or parent re-render โ not on its own
console.countstops climbing after the initial mount- Network tab confirms API calls fire once per action, not in a continuous stream
Prevention Checklist
- State setters belong in event handlers or effects โ never in the render body
- Event props take references:
onClick={fn}, not calls:onClick={fn()} - Inside
useEffect, prefer the functional updater form (prev => ...) and drop the state variable from the dependency array - Wrap objects and arrays used as effect dependencies in
useMemooruseCallbackto stabilize their references - Add
eslint-plugin-react-hooksto your project โ theexhaustive-depsrule catches most of these before they ever reach the browser

