preactjs/preact · critical · Error

Hook can only be invoked from render methods.

Error message

Hook can only be invoked from render methods.

What it means

Thrown from options._hook when a hook (useState, useEffect, etc.) is invoked but either there is no current component (`comp` falsy) or hooks are not currently allowed (`hooksAllowed` flag false). The hooksAllowed flag is set true during _diff/_render and reset to false after diffed completes; calling a hook outside that window — e.g. in an event handler, a setTimeout callback, or at module top level — is illegal because Preact has no component instance to bind the hook to.

Source

Thrown at debug/src/debug.js:270

		} 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);
	};

	// Ideally we'd want to print a warning once per component, but we
	// don't have access to the vnode that triggered it here. As a
	// compromise and to avoid flooding the console with warnings we
	// print each deprecation warning only once.
	const warn = (property, message) => ({
		get() {
			const key = 'get' + property + message;
			if (deprecations && deprecations.indexOf(key) < 0) {
				deprecations.push(key);
				console.warn(`getting vnode.${property} is deprecated, ${message}`);
			}
		},
		set() {

View on GitHub (pinned to e881e7e838)

Solutions

  1. Ensure all hook calls happen at the top level of a function component body, unconditionally, during render.
  2. Move logic that needs a hook out of event handlers and into the component body or a proper custom hook invoked during render.
  3. If you need state in a callback, lift it: store state in the component and reference via a ref or closure.
  4. Replace class components that mistakenly call hooks with function components, or convert the hook to class state.

Example fix

// before
function Form() {
  const [v, setV] = useState('');
  return <input onChange={(e) => {
    const [x] = useState(e.target.value); // illegal
    setV(x);
  }} />;
}

// after
function Form() {
  const [v, setV] = useState('');
  return <input value={v} onChange={(e) => setV(e.target.value)} />;
}
Defensive patterns

Strategy: validation

Validate before calling

// Enforce rules-of-hooks at lint time. Runtime fallback:
const __componentRenderDepth = { current: 0 };
function useSafeHook(factory) {
  if (__componentRenderDepth.current === 0) {
    throw new Error('Hook called outside of a component render. Move it into a function component body.');
  }
  return factory();
}
// Wrap components to set the depth marker around their render.

Prevention

When it happens

Trigger: Calling useState inside an onClick handler; calling useEffect inside a regular function that is not a component; calling a hook in a class component lifecycle; calling a hook conditionally after an early return (the hook runs after _diff set hooksAllowed, but the conditional skip is a different bug — this specific error is for out-of-render calls); calling a hook from a utility that runs outside the render pass.

Common situations: Extracting a 'custom hook' that is actually called from a non-render context; calling a hook in a setTimeout/Promise callback that escaped the render window; copy-pasting hook usage into an event handler; refactoring a component into a class but leaving hook calls in place; calling hooks from within useMemo/useCallback factories that run outside the hook registration phase.

Related errors


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