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

  1. Move setState out of render into an event handler or useEffect with a stable dependency array.
  2. If computing derived state, use useMemo or compute inline during render — do not setState during render.
  3. Audit useEffect deps: include all referenced reactive values or use a ref to break the cycle.
  4. 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

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


AI-assisted analysis of preactjs/preact@e881e7e838 (2026-08-13). Data as JSON: /api/errors/bfb91474f57662bb. Report an issue: GitHub.