Fix this error · React and Next.js
Fix: Too many re-renders. React limits the number of renders to prevent an infinite loop
React error #301 means state is set during render. The usual causes, like onClick={fn()}, a working fix for each, and how to catch it in production.
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
- Calling the handler instead of passing it.
onClick={handleClick()}oronClick={setCount(count + 1)}runs during render. - Setting state unconditionally in the component body. For example copying a prop into state on every render:
setName(props.name). - 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. - A helper or custom hook that sets state when called. A function such as
renderRows()oruseSomething()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
// 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.
// 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.
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.
// 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 windowerrorevent. 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 toreportError(). - Your error boundary catches it, including a Next.js
error.tsx. React and Next.js then only print it withconsole.error. BugTape records console output into a report but does not start one from it. Add one line to the boundary:
// 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 reports it on the Node.js runtime.
Add the browser SDK once (see the quickstart):
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 (301), checked 2 Nov 2026.
- React source, ReactFiberHooks.js and ReactFizzHooks.js (
RE_RENDER_LIMIT = 25), checked 2 Nov 2026. - React docs: useState troubleshooting, "Too many re-renders", checked 2 Nov 2026.
- React docs: You Might Not Need an Effect (adjusting state when a prop changes), checked 2 Nov 2026.
- React docs: createRoot
onCaughtErrorand ReactReactFiberErrorLogger.js, checked 2 Nov 2026.
Related errors
- React: Maximum update depth exceeded:
Maximum update depth exceeded - 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:
Objects are not valid as a React child - Next.js hydration mismatch:
Hydration failed because the server rendered HTML didn't match the client - Next.js 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.
Start free. Free plan · No credit card · Set up in minutes. Or try the live demo first.