{"record":{"id":"27b469b4d227282e","repo":"preactjs/preact","slug":"hook-can-only-be-invoked-from-render-methods","errorCode":null,"errorMessage":"Hook can only be invoked from render methods.","messagePattern":"Hook can only be invoked from render methods\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"debug/src/debug.js","lineNumber":270,"sourceCode":"\t\t} else {\n\t\t\trenderCount = 1;\n\t\t}\n\n\t\tif (renderCount >= 25) {\n\t\t\tthrow new Error(\n\t\t\t\t`Too many re-renders. This is limited to prevent an infinite loop ` +\n\t\t\t\t\t`which may lock up your browser. The component causing this is: ${getDisplayName(\n\t\t\t\t\t\tvnode\n\t\t\t\t\t)}`\n\t\t\t);\n\t\t}\n\n\t\tcurrentComponent = nextComponent;\n\t};\n\n\toptions._hook = (comp, index, type) => {\n\t\tif (!comp || !hooksAllowed) {\n\t\t\tthrow new Error('Hook can only be invoked from render methods.');\n\t\t}\n\n\t\tif (oldHook) oldHook(comp, index, type);\n\t};\n\n\t// Ideally we'd want to print a warning once per component, but we\n\t// don't have access to the vnode that triggered it here. As a\n\t// compromise and to avoid flooding the console with warnings we\n\t// print each deprecation warning only once.\n\tconst warn = (property, message) => ({\n\t\tget() {\n\t\t\tconst key = 'get' + property + message;\n\t\t\tif (deprecations && deprecations.indexOf(key) < 0) {\n\t\t\t\tdeprecations.push(key);\n\t\t\t\tconsole.warn(`getting vnode.${property} is deprecated, ${message}`);\n\t\t\t}\n\t\t},\n\t\tset() {","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/preactjs/preact/blob/e881e7e8386d7a430f87498a41c6a532992b4599/debug/src/debug.js#L252-L288","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nfunction Form() {\n  const [v, setV] = useState('');\n  return <input onChange={(e) => {\n    const [x] = useState(e.target.value); // illegal\n    setV(x);\n  }} />;\n}\n\n// after\nfunction Form() {\n  const [v, setV] = useState('');\n  return <input value={v} onChange={(e) => setV(e.target.value)} />;\n}","handlingStrategy":"validation","validationCode":"// Enforce rules-of-hooks at lint time. Runtime fallback:\nconst __componentRenderDepth = { current: 0 };\nfunction useSafeHook(factory) {\n  if (__componentRenderDepth.current === 0) {\n    throw new Error('Hook called outside of a component render. Move it into a function component body.');\n  }\n  return factory();\n}\n// Wrap components to set the depth marker around their render.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["preact","hooks","rules-of-hooks","usestate","useeffect","debug-build"],"backgroundTag":null,"analyzedSha":"e881e7e8386d7a430f87498a41c6a532992b4599","analyzedAt":"2026-08-13T04:27:04.532Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}