facebook/react · error · Error
Too many re-renders. React limits the number of renders to p
Error message
Too many re-renders. React limits the number of renders to prevent an infinite loop.
What it means
dispatchAction in Fizz throws when a state setter is called during render more than RE_RENDER_LIMIT (25) times. Each render-phase update makes React re-run the component immediately; an unconditional or insufficiently guarded setter call loops forever, and the limit stops the infinite loop.
Source
Thrown at packages/react-server/src/ReactFizzHooks.js:565
const ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
workInProgressHook.memoizedState = ref;
return ref;
} else {
return previousRef;
}
}
function dispatchAction<A>(
componentIdentity: Object,
queue: UpdateQueue<A>,
action: A,
): void {
if (numberOfReRenders >= RE_RENDER_LIMIT) {
throw new Error(
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
}
if (componentIdentity === currentlyRenderingComponent) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
action,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);View on GitHub (pinned to eafeac097b)
Solutions
- Guard render-phase updates: only call the setter when the value actually changed (if (state.source !== prevProp) setState(...)) so React settles after one re-render
- Derive the value during render instead of storing it — remove the state entirely
- If the update must happen later, move it to a client component event handler or effect
Example fix
// before
function Counter() {
const [n, setN] = useState(0);
setN(n + 1); // setter every render -> 25 re-renders -> throws
return <p>{n}</p>;
}
// after
function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
} Defensive patterns
Strategy: try-catch
Validate before calling
// The supported render-phase update pattern: only setState when the value changed.
const [state, setState] = useState({source: props.value, data: null});
if (state.source !== props.value) {
setState(s => ({...s, source: props.value})); // settles after one re-render
} Try / catch
const {pipe} = renderToPipeableStream(<App/>, {
onError(err) {
if (String(err.message).includes('Too many re-renders')) {
// An unguarded setter runs during render — log the component stack and fail the render.
logError(err);
}
},
}); Prevention
- Never call a setter unconditionally during render — guard it with a changed-value check
- Prefer deriving values during render over storing and syncing them in state
- Reserve updates for event handlers and effects; SSR should be a pure single pass
When it happens
Trigger: const [n, setN] = useState(0); setN(n + 1) in the body of a server-rendered component; setters invoked in render helpers without the change-guard the render-phase-update pattern requires; the same setter firing on every render pass so the count climbs to 25.
Common situations: Derived-state patterns ('sync state to props') written as an immediate setState during render; one-time initialization implemented as setState at render; client code with effect-based updates migrated into SSR'd components.
Related errors
- 407
- 485
- Cannot update optimistic state while rendering.
- 598
- Invalid hook call. Hooks can only be called inside of the bo
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/80892f8c5e844a3e.
Report an issue: GitHub.