React "Too Many Re-renders" Error Fix

September 22, 2026 · 15 views
React "Too Many Re-renders" Error Fix

You add a button, refresh the page, and React throws it at you immediately: Too many re-renders. React limits the number of renders to prevent an infinite loop. No stack trace pointing at your code, no obvious culprit — just a wall of red in the console. This error means a component's state is being updated during render itself, which triggers another render, which updates the state again, forever, until React's safety limit kicks in and stops the loop.

The good news is that the fix is almost always mechanical once you know what to look for. This guide walks through the exact causes, how to spot them fast, and how to structure state updates so this error can't happen again.

What actually causes "too many re-renders"

React renders a component, then commits it, then (optionally) runs effects. State updates are supposed to happen in response to something — a click, an effect, a subscription callback — not as a direct side effect of the render function running. When a state setter gets called unconditionally in the body of a component (not inside an event handler, not inside useEffect), React re-renders, hits that same setter call again, and loops.

The single most common trigger is this pattern:

function Counter() {
  const [count, setCount] = useState(0);

  // BUG: setCount is invoked, not passed, during render
  return <button onClick={setCount(count + 1)}>Add</button>;
}

onClick={setCount(count + 1)} calls setCount immediately while React is rendering the JSX, instead of waiting for a click. That single character difference — calling a function versus passing a reference to it — is behind a huge share of real-world reports of this error.

The fix: pass a function reference, don't call it

function Counter() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>Add</button>;
}

Wrapping the call in an arrow function means setCount only runs when the button is actually clicked. This is the fix for the vast majority of "too many re-renders" reports, and it's worth grepping your component for onClick={ followed directly by a function call (not an arrow function) as a first pass.

Other places this bug hides

The same root cause — a state setter running during render — shows up in a few other shapes that are easy to miss:

  • A setter called in the component body, outside any handler or effect — for example, syncing a prop to state with a plain assignment-style call instead of useEffect.
  • A useEffect with no dependency array that updates state unconditionally, so it re-runs after every render it causes.
  • Conditional logic during render that always evaluates true, such as updating state based on a comparison that never actually stabilizes.
  • A custom hook that calls its own setter on every invocation, which then loops through every component that uses it.

Here's the useEffect version, since it's the second most common cause after the inline-call bug:

function Profile({ userId }) {
  const [id, setId] = useState(userId);

  // BUG: no dependency array — runs after every render, sets state every time
  useEffect(() => {
    setId(userId);
  });

  return <div>{id}</div>;
}

Fixing it means adding a dependency array so the effect only runs when userId actually changes:

function Profile({ userId }) {
  const [id, setId] = useState(userId);

  useEffect(() => {
    setId(userId);
  }, [userId]);

  return <div>{id}</div>;
}

How to debug it when the cause isn't obvious

  1. Read the component stack in the error. React DevTools and the console warning usually name the component where the loop originates — start there instead of scanning the whole app.
  2. Search that component for every set* call. Check each one: is it inside a JSX prop expression that calls it directly, in the component body unconditionally, or in a useEffect missing a dependency array?
  3. Check custom hooks the component uses. If the loop doesn't show up in the component's own code, the setter call is probably inside a hook it calls.
  4. Add a console.log at the top of the component body (not inside a hook) to confirm it's actually looping and see how fast — this confirms you're looking at the right component before you start editing.
  5. Temporarily comment out state updates one at a time if multiple setters are in play, to isolate which one is unconditional.

Best practices to prevent it going forward

  • Always pass event handlers as function references (onClick={handleClick} or onClick={() => handleClick(arg)}), never call them directly in JSX.
  • Never call a state setter in the plain body of a component — only inside event handlers, effects with correct dependencies, or derived from another state update.
  • Give every useEffect an explicit dependency array; an empty array [] for "run once," or the real list of values it reads.
  • If you're deriving state from props, prefer computing the value directly during render instead of copying it into useState and syncing with an effect — it removes this entire class of bug.
  • Enable the react-hooks/exhaustive-deps ESLint rule, which catches most missing-dependency cases before they ship.

Frequently Asked Questions

Why does React limit re-renders instead of just letting the loop run? An uncontrolled render loop would freeze the tab and could crash the browser tab entirely. React caps consecutive renders and throws this error specifically so you get a recoverable error in development instead of a hung page.

Does this error only happen with useState? No — it happens with any state update mechanism that can trigger a re-render from within the render phase, including useReducer's dispatch, and state updates from context consumers or third-party state libraries that follow the same unconditional-call pattern.

Can useMemo or useCallback cause this error? Not directly — they don't hold state or trigger renders on their own. But a useMemo callback that calls a state setter as a side effect (which it shouldn't) can produce the same symptom, since useMemo runs during render too.

Why does the error only appear in development, not production builds? Production builds of React skip some of the extra checks and stack trace details, but the underlying infinite loop still happens — it just may manifest as a frozen page instead of a clear console error, which is exactly why this is worth fixing immediately when you see it in development rather than assuming it's dev-only noise.

Key Takeaways

The "too many re-renders" error almost always traces back to a state setter running unconditionally during render instead of inside an event handler or a properly-dependency-tracked effect. Start by checking JSX props for setters called directly instead of passed as references, then check useEffect calls for missing dependency arrays. Fix the setter's trigger condition rather than removing the state update entirely, and the loop resolves immediately.

#react #frontend #debugging #hooks #useeffect #usestate
Share this article:

0 Comments

No comments yet — be the first to share your thoughts.

Leave a comment

Never published.