preactjs/preact · critical · Error
Too many re-renders. This is limited to prevent an infinite
Error message
Too many re-renders. This is limited to prevent an infinite loop which may lock up your browser. The component causing this is: ${getDisplayName(vnode)} What it means
Thrown from options._render when the same component instance is re-rendered 25 or more times in a row. The debug layer counts consecutive renders of the same component (currentComponent) and aborts once renderCount reaches 25, naming the offending component. This is a hard cap to prevent an infinite update loop from locking the browser, mirroring React's equivalent guard.
Source
Thrown at debug/src/debug.js:257
};
let renderCount = 0;
let currentComponent;
options._render = vnode => {
if (oldRender) {
oldRender(vnode);
}
hooksAllowed = true;
const nextComponent = vnode._component;
if (nextComponent === currentComponent) {
renderCount++;
} else {
renderCount = 1;
}
if (renderCount >= 25) {
throw new Error(
`Too many re-renders. This is limited to prevent an infinite loop ` +
`which may lock up your browser. The component causing this is: ${getDisplayName(
vnode
)}`
);
}
currentComponent = nextComponent;
};
options._hook = (comp, index, type) => {
if (!comp || !hooksAllowed) {
throw new Error('Hook can only be invoked from render methods.');
}
if (oldHook) oldHook(comp, index, type);
};
View on GitHub (pinned to e881e7e838)
Solutions
- Move setState out of render into an event handler or useEffect with a stable dependency array.
- If computing derived state, use useMemo or compute inline during render — do not setState during render.
- Audit useEffect deps: include all referenced reactive values or use a ref to break the cycle.
- Add a guard so updates only fire when the value actually changes: `if (x !== prevX) setX(x)`.
Example fix
// before
function Counter() {
const [n, setN] = useState(0);
setN(n + 1); // infinite loop
return <div>{n}</div>;
}
// after
function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
} Defensive patterns
Strategy: validation
Validate before calling
// Defensive pattern: never setState during render unconditionally.
function useStableState(initial) {
const [state, setState] = useState(initial);
const lastRef = useRef(state);
// only update if changed, preventing loops
const safeSet = useCallback((next) => {
const v = typeof next === 'function' ? next(lastRef.current) : next;
if (!Object.is(v, lastRef.current)) {
lastRef.current = v;
setState(v);
}
}, []);
return [state, safeSet];
} Prevention
- Never call setState in the render body — only in event handlers or effects.
- Audit useEffect dependency arrays for missing values that the effect sets.
- Compute derived data with useMemo during render, not by syncing into state.
- Add an eslint-plugin-react-hooks equivalent (preact/hooks rules) to flag violations statically.
When it happens
Trigger: Calling setState unconditionally in the render body: `function C() { const [n,setN]=useState(0); setN(n+1); return <div/>; }`; useEffect with no/incorrect deps that always triggers a state update; setState inside a component constructor or componentWillMount; an effect that writes to state it also depends on; a reducer that never converges.
Common situations: Misplaced setState in render instead of an event handler or effect; useEffect dependency array that omits a value the effect sets; derived state pattern using setState during render; subscription handler that updates state on every tick including the tick it just caused; bad useMemo/useCallback dependency that cascades.
Related errors
- Hook can only be invoked from render methods.
- Undefined parent passed to render(), this is the second argu
- Expected a valid HTML node as a second argument to render. R
- Undefined component passed to createElement() You likely fo
- Invalid type passed to createElement(): ${type} Did you acc
AI-assisted analysis of preactjs/preact@e881e7e838 (2026-08-13).
Data as JSON: /api/errors/bfb91474f57662bb.
Report an issue: GitHub.