Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
Your component is updating state during render, which triggers another render, which updates state again β infinite loop. React catches this and stops it.
Cause 1: Calling a Function Instead of Passing a Reference
The most common cause.
// β This CALLS handleClick on every render
<button onClick={handleClick()}>Click me</button>
// β
This PASSES handleClick as a reference
<button onClick={handleClick}>Click me</button>
// β
If you need to pass arguments, wrap in arrow function
<button onClick={() => handleClick(id)}>Click me</button>
The difference: handleClick() runs immediately. handleClick or () => handleClick(id) runs only when clicked.
Cause 2: setState During Render
// β Setting state directly in the component body
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // π₯ runs every render!
return <div>{count}</div>;
}
// β
Set state in an event handler or useEffect
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
Cause 3: useEffect Without Dependency Array
// β Runs after EVERY render, sets state, triggers re-render β loop
useEffect(() => {
setData(fetchData());
});
// β
Empty array = run once on mount
useEffect(() => {
fetchData().then(setData);
}, []);
// β
With dependencies = run when dependencies change
useEffect(() => {
fetchData(userId).then(setData);
}, [userId]);
Cause 4: Object/Array in Dependency Array
// β New object every render β useEffect runs every render β loop
function MyComponent({ filters }) {
const options = { ...filters, limit: 10 }; // new object each render
useEffect(() => {
fetchData(options).then(setData);
}, [options]); // π₯ options is always "new"
}
// β
Memoize the object
function MyComponent({ filters }) {
const options = useMemo(
() => ({ ...filters, limit: 10 }),
[filters]
);
useEffect(() => {
fetchData(options).then(setData);
}, [options]);
}
Cause 5: Conditional setState That Always Triggers
// β If items always has length > 0, this loops forever
useEffect(() => {
if (items.length > 0) {
setHasItems(true); // triggers re-render β useEffect runs again
}
}, [items]);
// β
Only set state if it actually changed
useEffect(() => {
const newValue = items.length > 0;
setHasItems(prev => prev === newValue ? prev : newValue);
}, [items]);
// β
Or better: derive it instead of storing in state
const hasItems = items.length > 0; // no state needed!
Debugging Tips
- Add
console.log('render')at the top of your component β if it prints endlessly, you have the loop - Check every
onClickβ are you callingfn()instead of passingfn? - Check every
useEffectβ does it have a dependency array? - Check every
setStatecall β is it inside a handler/effect, or in the render body? - Use React DevTools Profiler to see whatβs causing re-renders