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
- Ensure all hook calls happen at the top level of a function component body, unconditionally, during render.
- Move logic that needs a hook out of event handlers and into the component body or a proper custom hook invoked during render.
- If you need state in a callback, lift it: store state in the component and reference via a ref or closure.
- 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
- Only call hooks at the top level of a function component or custom hook — never in loops, conditions, or callbacks.
- Never call hooks from event handlers, setTimeout, or Promise callbacks.
- Enable the preact/hooks eslint rules to catch violations at compile time.
- If you need state in a callback, store it in the component and reference via a ref/closure.
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
- Too many re-renders. This is limited to prevent an infinite
- 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/27b469b4d227282e.
Report an issue: GitHub.