How to Fix “Too Many Re-Renders” in React (Infinite Loop in useEffect)
Why Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop from happening, and the 4 clean patterns fix every React developer needs.
You open your browser console during local development, hit refresh, and are instantly greeted by a brutal wall of red text:
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
Your CPU fan starts spinning, the UI freezes, and React forcibly shuts down execution.
This error is one of the most common friction points in React development, regardless of whether you are building with React 18 or React 19. The good news? React isn’t crashing randomly — it is actively saving your user’s browser from completely hanging.
Understanding why this loop occurs and how to fix it comes down to recognizing four recurring code patterns.
What Is React Doing Under the Hood?
React operates on a simple fundamental rule: State changes trigger component re-renders.
When you call a state updater like setCount(1), React schedules a render phase to recalculate the Virtual DOM. If during that render phase — or immediately after it executes — another state update is triggered before waiting for user interaction or an async event, React gets stuck in a loop:
$$\text{Render} \longrightarrow \text{State Update} \longrightarrow \text{Re-render} \longrightarrow \text{State Update} \longrightarrow \text{Infinite Loop}$$
To prevent your system memory from completely locking up, React places a hard limit on continuous recursive re-renders (typically around 50 consecutive renders) before throwing this explicit error.
Let’s break down the four exact code patterns that cause this and how to rewrite them correctly.
Pattern 1: Executing Event Handlers Immediately (The Classic Trap)
This is responsible for nearly 70% of all “Too Many Re-Renders” errors, especially when developers start passing arguments to handlers.
The Bug
Why it fails
Parentheses () mean invocation. When React evaluates the return block to render the JSX, it executes setItemsCount(itemsCount + 1) right away. This state update immediately queues another render, which evaluates the JSX again, triggering setItemsCount once more in an instant loop.
The Fix
Pass a function reference or wrap the handler in an inline anonymous arrow function so it only runs when the button is physically clicked.
Pattern 2: Direct State Updates inside Component Body
Updating state directly in the top-level execution path of a component function is a guaranteed way to break React’s render lifecycle.
The Bug
Why it fails
Every time UserStatus renders, it evaluates the if condition and immediately invokes setRole(‘admin’). Calling state updaters directly inside the body forces React to re-evaluate the component immediately, generating an infinite render cycle.
The Fix
If you need to derive state from existing props, do not use useState or useEffect at all. Calculate it directly during render (derived state):
If you must run a side effect based on prop changes, wrap the state update inside a controlled useEffect:
Pattern 3: The useEffect Dependency Loop
Get Selvarasa Thamilkkumaran’s stories in your inbox
Join Medium for free to get updates from this writer.
A frequent culprit in data-fetching components is modifying a state value inside useEffect while listing that same state value as a dependency.
The Bug
Why it fails
- The component mounts and runs useEffect.
- setUsers updates state, triggering a re-render.
- React checks the dependency array [users]. Since users updated, useEffect runs again.
- Steps 2 and 3 repeat infinitely.
The Fix
Remove the state variable from the dependency array if you only want the effect to run on component mount:
Pattern 4: Reference Instability with Non-Primitive Dependencies
JavaScript compares primitives (strings, numbers, booleans) by value, but arrays, objects, and functions are compared by reference.
The Bug
Why it fails
Even if userId doesn’t change, { id: userId, depth: 2 } creates a brand-new object in memory every time ProfileDashboard evaluates. React performs a shallow comparison on useEffect dependencies (Object.is(oldOptions, newOptions)), which returns false. useEffect fires, state updates, and the cycle repeats.
The Fix
Pass primitive properties directly into the dependency array, or wrap non-primitive objects using useMemo:
The 30-Second Debugging Checklist
When you see the “Too many re-renders” red screen in your application, check these four areas immediately:
- Check your JSX events: Are you writing onClick={handleClick()} instead of onClick={handleClick} or onClick={() => handleClick(id)}?
- Check your top-level component scope: Are you invoking any setState functions outside of an event handler or useEffect block?
- Check your useEffect dependencies: Are you setting a state variable inside an effect while keeping that same variable in the dependency array?
- Check for inline objects or functions: Are you creating objects or arrays inside component bodies and passing them directly into useEffect dependency arrays?
Fixing these subtle reference and execution errors will instantly eliminate infinite render cascades and keep your app performing smoothly.








