React "Invalid Hook Call": The 3 Causes and How to Fix Each
React throws 'Invalid hook call' when a hook runs outside a component, when hooks are called conditionally or in a regular function, or when two copies of React are loaded. How to tell which one you have and fix it — including npm ls react and linked packages.
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
The error helpfully lists its three causes. Here's how to check each one.
First, what counts as a hook
Any function starting with use — React's own (useState, useEffect, useContext) and custom ones (useAuth, useQuery). (React custom hooks)
Cause 1: breaking the Rules of Hooks (most common)
Hooks may only be called:
- at the top level of a function component or custom hook,
- not inside loops, conditions, nested functions, event handlers, or regular (non-component) functions.
Calling a hook in a regular function
function getUser() {
const [user] = useState(null) // ❌ not a component or hook
}
Fix: make it a custom hook (name starts with use) and call it from a component, or move the logic into the component.
Calling a hook inside an event handler
<button onClick={() => {
const nav = useNavigate() // ❌
nav('/home')
}}>
Fix: call the hook at the top of the component, use the result in the handler:
const nav = useNavigate()
<button onClick={() => nav('/home')}>
Calling a component like a function
{Header()} // ❌ Header uses hooks, but this calls it as a plain function
<Header /> // ✅
Calling a hook in a class component
Hooks only work in function components.
Conditional hooks
if (!user) return null
const [tab, setTab] = useState('home') // ❌ hook after an early return
This usually gives a different error ("Rendered more hooks than during the previous render"), but the fix is the same: put all hooks before any early return.
The ESLint plugin eslint-plugin-react-hooks catches all of these as you type. Most project templates include it. (Linters and formatters)
Cause 2: two copies of React
Hooks rely on a single shared React instance. If your app bundles two copies, hooks from one can't find the other's state.
Check:
npm ls react
If you see react listed more than once at different versions, that's it. Common reasons:
- A library lists
reactas a regular dependency instead of a peerDependency, so it brings its own copy. - You're developing a component library and used
npm link— the linked package uses its ownnode_modules/react. - A monorepo where packages have separate React installs. (Monorepo vs polyrepo)
Fixes:
Update the offending library, or use npm
overridesto force one version:"overrides": { "react": "$react", "react-dom": "$react-dom" }For linked packages, make them use the app's React (bundler alias, or link React back from the app).
Delete
node_modulesand the lock file's stale entries and reinstall. (node_modules explained)
Cause 3: mismatched React and React DOM versions
react and react-dom must be the same version:
npm ls react react-dom
If they differ, install matching versions:
npm install react@latest react-dom@latest
Next.js: hooks in server components
In the Next.js App Router, components are server components by default, and they can't use useState, useEffect or other client hooks. Next.js usually gives a clearer error for this, but if you see hook errors in an App Router project, add "use client" at the top of the file that uses hooks. (React Server Components explained)
Quick checklist
- Is the hook called at the top level of a component or
use…function? - Is anything calling a component as a function?
npm ls react— one copy?reactandreact-domversions match?- Next.js App Router — does the file need
"use client"?
EasySpawn runs your React app next to Claude Code on a persistent server — it can run npm ls react, find the duplicate and fix it while you watch. See how it works or join the waitlist.
Related: React useState Explained · React useEffect Explained · React Server Components Explained · React Custom Hooks
Keep reading
"ReferenceError: window is not defined" in Next.js: How to Fix It
window, document and localStorage only exist in the browser. Next.js renders components on the server first, where they don't exist. Why it happens, and the fixes: useEffect, 'use client', typeof checks, dynamic imports with ssr: false, and libraries that touch window on import.
"Unexpected Token < in JSON at Position 0": What It Means and How to Fix It
Your code expected JSON and got HTML — almost always an error page or your app's index.html. Why it happens (wrong URL, 404, server error, SPA fallback, login redirect), how to see what the server actually sent, and how to parse responses safely.