Three documented causes: calling a hook outside a component function, breaking the Rules of Hooks order, or — the sneaky one — two copies of React in the dependency tree. The first two are code bugs; the third is an npm problem.
A useState/useEffect in a plain function, a callback, early-returned before the hook, or behind an if. Hooks must run unconditionally, in the same order, during render.
npm ls react shows 2+ versions: a UI library bundling its own React, a hoisting failure, or a monorepo with inconsistent versions. Your component's hook and the renderer's dispatcher come from different Reacts — the call is invalid by identity, not by placement.
# eslint-plugin-react-hooks catches placement/order violations automatically: npx eslint src --ext .js,.jsx
npm ls react # any nested second copy = the identity mismatch
rm -rf node_modules package-lock.json && npm install # monorepo: align versions + resolutions/overrides field
# package.json "resolutions": { "react": "^18.2.0" } (yarn) or "overrides" (npm); or peerDependencies done right in the library
Minified error #321 IS invalid-hook-call — production builds hide the text, the code identifies it. React's docs list exactly these three causes, in this order of likelihood. The two-React case is the one that 'makes no sense' from a code review: the component is textbook-perfect and the error persists. npm ls react before re-reading your hooks.
Then it's the React-identity case: check npm ls react for a second copy. The hook IS being called correctly — just against a different React instance than the one rendering it.
A clean reinstall re-hoists the tree and can collapse the duplicate React a library dragged in. It's legitimate for this error — but resolutions/overrides in package.json make the fix durable.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.