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

A component calls a state setter while it renders, so every render schedules another render. React stops after 25 attempts and throws. The fix that works most often is to pass the event handler instead of calling it: `onClick={handleClick}` or `onClick={() => setOpen(true)}`, never `onClick={setOpen(true)}`.

| Fact | Detail |
|---|---|
| Error | `Too many re-renders. React limits the number of renders to prevent an infinite loop.` |
| In production builds | `Minified React error #301` |
| Where you see it | Browser console, the Next.js error overlay, server logs when it happens during server rendering |
| Usual cause | A `set` function called in the component body, often through `onClick={handler()}` |
| Usual fix | Pass a function to the event prop, and compute derived values instead of storing them |
| BugTape catches it | Yes, automatically, when no error boundary of yours catches it. One line in your boundary otherwise |

## What the error means

Calling a state setter during render tells React to render the component again before it finishes. React allows this so a component can adjust its state once. When the component sets state again on that next render, and again on the one after, React counts the attempts. In the React source the limit is 25 (`RE_RENDER_LIMIT`). On the next attempt it throws this error.

The error is thrown during render, so the component never shows. Without an error boundary, the whole root unmounts and the user sees a blank area or a blank page.

The same check exists in React's server renderer, so in Next.js the error can also come from the server.

## Common causes

1. **Calling the handler instead of passing it.** `onClick={handleClick()}` or `onClick={setCount(count + 1)}` runs during render.
2. **Setting state unconditionally in the component body.** For example copying a prop into state on every render: `setName(props.name)`.
3. **A condition that stays true.** `if (items.length > 0) setHasItems(true)` runs on every render once it is true, because the setter is called again with each render.
4. **A helper or custom hook that sets state when called.** A function such as `renderRows()` or `useSomething()` calls a setter in its own body, and the component calls it while rendering.

## How to fix it

### Pass the event handler, do not call it

```tsx
// Before: setOpen runs during render
<button onClick={setOpen(true)}>Open</button>
<button onClick={handleDelete(id)}>Delete</button>

// After: React calls these on click
<button onClick={() => setOpen(true)}>Open</button>
<button onClick={() => handleDelete(id)}>Delete</button>
```

The React docs give this as the usual cause. If you cannot find it, expand the error in the browser console and look through the stack for the `set` call.

### Compute derived values during render

If a value can be worked out from props or other state, it does not need its own state.

```tsx
// Before
function Cart({ items }: { items: Item[] }) {
  const [total, setTotal] = useState(0);
  setTotal(items.reduce((sum, i) => sum + i.price, 0)); // runs on every render
  return <p>Total: {total}</p>;
}

// After
function Cart({ items }: { items: Item[] }) {
  const total = items.reduce((sum, i) => sum + i.price, 0);
  return <p>Total: {total}</p>;
}
```

Wrap the calculation in `useMemo` only if it is slow.

### Guard a state adjustment so it runs once

When state really must reset after a prop changes, store the previous prop and compare. The setter then runs only on the render where the prop changed.

```tsx
function List({ items }: { items: Item[] }) {
  const [selection, setSelection] = useState<Item | null>(null);
  const [prevItems, setPrevItems] = useState(items);

  if (items !== prevItems) {
    setPrevItems(items);
    setSelection(null);
  }
  // ...
}
```

Often a `key` is simpler. `<List key={listId} items={items} />` gives the component fresh state whenever `listId` changes.

### Move setters out of helpers called during render

A helper that renders markup must not change state. Return values from it, and change state in an event handler or an Effect.

```tsx
// Before
function renderRows() {
  setRowCount(rows.length);
  return rows.map((r) => <Row key={r.id} row={r} />);
}

// After
const rowCount = rows.length;
const rowElements = rows.map((r) => <Row key={r.id} row={r} />);
```

## How to catch it in production with BugTape

In production React throws `Minified React error #301` with a link to the full text. What happens to the error depends on whether an error boundary catches it.

- **No error boundary of yours catches it.** React 19 passes the error to `reportError()`, which fires a window `error` event. React 18 rethrows it as an uncaught error. In both cases the BugTape browser SDK reports it automatically, with the page URL, the stack and the clicks and requests before it. In the Next.js App Router, an error that only the built-in boundary catches also goes to `reportError()`.
- **Your error boundary catches it**, including a Next.js `error.tsx`. React and Next.js then only print it with `console.error`. BugTape records console output into a report but does not start one from it. Add one line to the boundary:

```tsx
// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';

export default function Error({ error, retry }: { error: unknown; retry: () => void }) {
  useEffect(() => {
    reportError(error); // fires the window error event BugTape listens for
  }, [error]);
  return <button onClick={() => retry()}>Try again</button>;
}
```

For a plain React 19 app, you can do the same for every boundary at once with `createRoot(el, { onCaughtError: (error) => reportError(error) })`.

The SDK records nothing before `init()` runs, so an error during the very first render on page load can come before BugTape starts. Errors on later renders and client navigations are caught. When the loop happens during server rendering in Next.js, the [Node SDK's `onRequestError` hook](/docs/node/) reports it on the Node.js runtime.

Add the browser SDK once (see the [quickstart](/docs/quickstart/)):

```ts
import { init } from '@bugtape/sdk';

init({ apiKey: 'YOUR_PROJECT_KEY', release: import.meta.env.VITE_GIT_SHA });
```

Repeats group into one issue with a count. Set `release` so that when you mark the issue fixed, a report from a newer release reopens it as a regression. Your coding agent can read the issue over MCP (`npx -y bugtape connect claude`, or `cursor` or `codex`) and propose the change. You review the pull request.

## FAQ

### What is React error 301?

It is the production code for `Too many re-renders. React limits the number of renders to prevent an infinite loop.` Production builds replace the message with the number and a link to `react.dev/errors/301`. Run the app in development to see the full message and the component stack.

### What is the difference between "Too many re-renders" and "Maximum update depth exceeded"?

"Too many re-renders" (#301) means a component set state while rendering itself. "Maximum update depth exceeded" (#185) means updates kept coming after each commit, usually from `componentDidUpdate`, `useLayoutEffect` or a store subscription that sets state every time. The fixes are different, so check which one you have.

### Why does onClick={handleClick()} cause an infinite loop?

The parentheses call `handleClick` while React builds the button. If the function sets state, that triggers another render, which calls it again. Without the parentheses React stores the function and calls it only when the user clicks.

### Does Strict Mode cause this error?

No. Strict Mode calls your component function one extra time in development to find side effects. That extra call does not repeat. If you see this error, something in the component sets state on every render.

## Sources

- [React error codes](https://github.com/facebook/react/blob/main/scripts/error-codes/codes.json) (301), checked 2 Nov 2026.
- [React source, ReactFiberHooks.js](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js) and [ReactFizzHooks.js](https://github.com/facebook/react/blob/main/packages/react-server/src/ReactFizzHooks.js) (`RE_RENDER_LIMIT = 25`), checked 2 Nov 2026.
- [React docs: useState troubleshooting, "Too many re-renders"](https://react.dev/reference/react/useState#im-getting-an-error-too-many-re-renders), checked 2 Nov 2026.
- [React docs: You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) (adjusting state when a prop changes), checked 2 Nov 2026.
- [React docs: createRoot `onCaughtError`](https://react.dev/reference/react-dom/client/createRoot) and [React `ReactFiberErrorLogger.js`](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberErrorLogger.js), checked 2 Nov 2026.

## Related errors

- [React: Maximum update depth exceeded](/guides/react-maximum-update-depth-exceeded/): `Maximum update depth exceeded`
- [React: Invalid hook call](/guides/react-invalid-hook-call/): `Invalid hook call. Hooks can only be called inside of the body of a function component.`
- [React: Objects are not valid as a React child](/guides/react-objects-not-valid-as-child/): `Objects are not valid as a React child`
- [Next.js hydration mismatch](/guides/nextjs-hydration-mismatch/): `Hydration failed because the server rendered HTML didn't match the client`
- [Next.js Server Components render error](/guides/nextjs-server-components-render-error/): `An error occurred in the Server Components render. The specific message is omitted in production builds`

All guides: [Fix an error](/guides/).

[Start free](/console/register?utm_source=bugtape_seo&utm_campaign=error-guides). Free plan · No credit card · Set up in minutes. Or [try the live demo](/demo/?from=landing&start=1#capture) first.
