> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Too Many Re-renders: Finding the Infinite Loop in React
- URL: https://debugly.dev/react-too-many-re-renders-infinite-loop/
- Published: 2025-10-08T09:00:00.000Z
- Updated: 2026-10-17T05:59:11.000Z
- Description: React caught the loop and stopped it. Here are the five ways to create one and how the Profiler tells you which you have.
- Author: Rohit Bhadani
- Tags: React, Error Autopsy, JavaScript, Performance

## The short version

```
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

```

Something sets state during render, or an effect sets state that causes the effect to run again.

I have lost on-call weeks to this exact shape of slow burn, so the order below is the order I fix it in.

The five causes:

1. **Calling a function instead of passing it**: `onClick={handleClick()}`
2. **`setState` directly in the component body**
3. **An effect with a missing or unstable dependency**
4. **A new object or array in a dependency array**
5. **A parent re-rendering that recreates a prop the child depends on**

Find it fast by opening React DevTools Profiler, enabling "Record why each component rendered", and looking at the render reason.

Tested on React 19.1.

## Cause 1: calling instead of passing

```jsx
// wrong: calls handleClick during render, every render
<button onClick={handleClick()}>Save</button>

// right
<button onClick={handleClick}>Save</button>

```

If `handleClick` calls `setState`, you have an infinite loop immediately. This is the most common cause and the easiest to miss when you need to pass an argument:

```jsx
// wrong
<button onClick={deleteItem(id)}>Delete</button>

// right
<button onClick={() => deleteItem(id)}>Delete</button>

```

## Cause 2: setState in the component body

```jsx
function Profile({ user }) {
  const [name, setName] = useState("");
  setName(user.name);        // runs on every render, triggers a render
  return <h1>{name}</h1>;
}

```

Render, set state, re-render, set state, forever.

This is almost always an attempt to derive state from props, and the fix is to not store it as state at all:

```jsx
function Profile({ user }) {
  const name = user.name;    // just use it
  return <h1>{name}</h1>;
}

```

If it needs a transformation, compute it during render:

```jsx
const displayName = user.name.trim() || "Anonymous";

```

There is a legitimate pattern for adjusting state when a prop changes, and it requires a guard:

```jsx
const [prevUserId, setPrevUserId] = useState(user.id);
if (user.id !== prevUserId) {
  setPrevUserId(user.id);
  setSelection(null);        // reset when the user changes
}

```

The condition is what stops the loop. React explicitly supports setting state during render when it is guarded like this, and it is better than an effect because it avoids a wasted render pass.

## Cause 3: the effect that triggers itself

```jsx
useEffect(() => {
  setCount(count + 1);
});

```

No dependency array means the effect runs after every render, and it sets state, which causes a render.

```jsx
useEffect(() => {
  fetchData().then(setData);
}, [data]);              // depends on what it sets

```

Fetch, set data, data changed, effect runs, fetch again. This one loops more slowly and can look like a performance problem rather than an error, and it will hammer your API.

The fix is usually a correct dependency array, and often the realisation that the effect should not depend on its own output.

## Cause 4: a new reference every render

The subtle one, and the one that survives review.

```jsx
function Search({ filters }) {
  const options = { limit: 10, ...filters };     // new object every render

  useEffect(() => {
    fetchResults(options).then(setResults);
  }, [options]);                                  // never equal to last time
}

```

React compares dependencies with `Object.is`. A newly created object is never equal to the previous one, even with identical contents. So the effect runs every render, sets state, renders again.

Same trap with arrays, and with functions:

```jsx
const handleChange = () => { ... };      // new function every render
useEffect(() => { subscribe(handleChange); }, [handleChange]);

```

Three fixes, in order of preference.

**Depend on primitives:**

```jsx
useEffect(() => {
  fetchResults({ limit: 10, ...filters }).then(setResults);
}, [filters.category, filters.minPrice]);

```

**Move it outside the component** if it does not depend on props or state:

```jsx
const DEFAULT_OPTIONS = { limit: 10 };

```

**Memoise as a last resort:**

```jsx
const options = useMemo(() => ({ limit: 10, ...filters }), [filters.category]);

```

`useMemo` is the answer people reach for first and it should be last, because it adds a dependency array of its own that can have the same problem one level up.

If you are on React 19 with the compiler enabled, a lot of this memoisation is handled automatically. It does not fix a genuinely incorrect dependency, only unnecessary re-creation.

## Cause 5: a parent recreating props

```jsx
function Parent() {
  return <Child config={{ theme: "dark" }} onSave={() => save()} />;
}

```

Both props are new references on every parent render. If `Child` has an effect depending on either, it runs every time the parent renders, regardless of `React.memo`.

`React.memo` does a shallow comparison, and a new object fails it. So the memo does nothing and people conclude memo is broken.

Same fixes: hoist constants out, `useCallback` for handlers that are dependencies, or restructure so the child does not need the object.

## Finding it with the Profiler

Open React DevTools, Profiler tab, gear icon, enable **"Record why each component rendered"**.

Record a few seconds of the loop. Each render shows a reason:

- **Props changed: config** tells you exactly which prop, and if the value looks identical, it is a reference problem
- **Hook 3 changed** points at a specific hook by index, counted in declaration order
- **The parent rendered** means the child is fine and you should look up the tree

That last one matters. Chasing a re-render in a child when the parent is the cause wastes a lot of time, and the Profiler tells you in one click.

For a loop that crashes before you can profile, comment out effects one at a time. Crude and it converges in a few reloads.

## A useful debugging hook

```jsx
function useWhyDidYouUpdate(name, props) {
  const prev = useRef();
  useEffect(() => {
    if (prev.current) {
      const changed = {};
      for (const key of Object.keys({ ...prev.current, ...props })) {
        if (prev.current[key] !== props[key]) {
          changed[key] = { from: prev.current[key], to: props[key] };
        }
      }
      if (Object.keys(changed).length) console.log("[why-update]", name, changed);
    }
    prev.current = props;
  });
}

```

Call it at the top of a suspect component with its props. It logs exactly which prop changed identity, and seeing `from: {limit: 10}, to: {limit: 10}` with different references makes the reference problem obvious in a way that reading code does not.

## Prevention

**`react-hooks/exhaustive-deps`.** The lint rule. Turn it on and do not suppress it casually. Most infinite loops from effects are dependency arrays that were wrong in a way the rule catches.

When the rule complains and adding the dependency creates a loop, that is a signal the effect is structured wrong, not that the rule is wrong. It is telling you the effect depends on something it changes.

**Ask whether you need the effect at all.** A large share of these are effects that should not exist: deriving state from props, transforming data for rendering, or responding to a user event. The [React documentation section on this](https://react.dev/learn/you-might-not-need-an-effect?ref=debugly.dev) is genuinely worth reading and it eliminates the problem rather than fixing it.

**Prefer computing during render** to storing derived values in state. Fewer renders, no synchronisation bugs, less code.

**Use a data library for fetching.** TanStack Query or SWR handle caching, deduplication, and cancellation. Manual fetching in `useEffect` is a category of code that is easy to get subtly wrong, and the same reasoning applies as with [effects running twice under Strict Mode](https://debugly.dev/why-useeffect-runs-twice-react/).

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)