
How to Fix the Too Many Re-renders Error in React Apps
React's reactive model is built around automated component updates. When state changes, React schedules a re-render. However, if your component updates its state during the rendering cycle itself, it triggers a secondary render. If that secondary render triggers another update, you enter an infinite loop.
To protect the browser from locking up, React raises a runtime error: "Too many re-renders. React limits the number of renders to prevent an infinite loop."
In this guide, we will analyze the core causes of this error, examine common code traps (such as incorrect event bindings and useEffect dependency loops), and learn how to resolve them.
The Core Cause: State Updates During Render
React components are functions that must act as pure functions during the rendering pass. They should compute their output (JSX) based on props and current state, without causing side effects.
If you invoke a state-setter function (e.g., setCount(...)) directly inside the body of your component, you violate this rule:
// INSECURE: Triggers infinite loop
export function InfiniteCounter() {
const [count, setCount] = useState(0);
// Invoking setter during rendering
setCount(count + 1);
return <div>{count}</div>;
}Every time the function executes, it calls setCount, scheduling a new render, which runs the function again, calling setCount indefinitely.
Common Code Traps and How to Resolve Them
Let's look at the three most common scenarios where this error occurs.
Trap 1: Incorrect Event Handler Binding
This is the most common mistake for developers transitioning to React. It happens when you call a state-setter function immediately instead of passing it as a callback reference.
// INSECURE: Calls setCount immediately when the component renders
<button onClick={setCount(count + 1)}>Increment</button>Because setCount(count + 1) contains trailing parentheses, JavaScript executes the function during the rendering phase. The button does not wait for a click; the trigger fires automatically on load.
- The Fix: Wrap the setter inside an anonymous arrow function, passing the reference instead of the evaluated output:
// SECURE: Passes a function reference that executes only on click
<button onClick={() => setCount(count + 1)}>Increment</button>Trap 2: Infinite useEffect Dependency Loops
useEffect allows you to run side effects when dependencies change. If you update a state variable inside a hook, and that state variable is declared in the hook's dependency array, you create a loop.
// INSECURE: Trigger loop
export function UserDashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then((res) => {
// Updates state
setData(res);
});
}, [data]); // Loop triggered because dependency 'data' updates on fetch complete
}- The Fix: If the effect only needs to execute once when the component mounts, set the dependency array to empty
[]. If the effect depends on specific identifiers (likeuserId), only include those identifiers in the array:
// SECURE: Runs only when userId changes
useEffect(() => {
fetchData(userId).then((res) => {
setData(res);
});
}, [userId]);Trap 3: Updating Parent State During Child Render
If a child component receives a callback prop that updates the parent component's state, calling that callback inside the child's render cycle (outside of event handlers) triggers the error.
// INSECURE
export function ChildComponent({ onRender }) {
onRender(); // Triggers parent state update during child render
return <div>Child</div>;
}- The Fix: If you must synchronize state across parent and child components, perform the update inside event handlers or wrap the child initialization logic inside
useEffect:
// SECURE: Updates parent state safely after mounting completes
useEffect(() => {
onRender();
}, [onRender]);Conclusion
The "Too many re-renders" error is React's safety net preventing browser crashes. To avoid it, ensure your rendering functions remain pure. Never call state setters directly inside the component body, always wrap click event handlers in anonymous functions, double-check your useEffect dependencies to prevent recursive loops, and update parent states safely after mounting is complete.