Fix: Invalid hook call. Hooks can only be called inside of the body of a function component
Part of: React & Frontend Errors
Quick Answer
How to fix the React Invalid hook call error caused by mismatched React versions, duplicate React copies, calling hooks outside components, and class component usage.
The Error
Your React app crashes with:
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 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.This error fires when React detects a hook (useState, useEffect, useContext, etc.) being called in a context where hooks are not allowed. React’s internal hook dispatcher is not set up, so the hook call fails.
Why This Happens
React hooks rely on an internal dispatcher that is only active during the rendering of a function component. When you call a hook outside this context, the dispatcher is null and React throws this error. The dispatcher is stored on a shared ReactCurrentDispatcher.current field inside the React package’s internals. That is why “multiple copies of React” is fatal: copy A sets the dispatcher, copy B’s hooks read from a different ReactCurrentDispatcher that was never set, and you get the invalid call.
The single most useful diagnostic is to inspect the actual identity of React and ReactDOM in your bundle. If React.version differs between the entry file and a third-party component, you have a duplicate. If React.version matches but the hook still fails, you have multiple instances of the same version — bundling has split React across two chunks. Both produce the same error message but require different fixes (Fix 2 covers both).
This error is also one of the few places React’s runtime does deep work to give you a useful message. The hint about three possible causes is generated by react-dom’s development build only; in production you get a shorter message linking to the React error decoder. If you only see the short version, switch to a dev build to triage.
The three causes listed in the error message cover most scenarios:
- Mismatched versions.
reactandreact-domare at different major versions. For example,react@18withreact-dom@17. - Multiple copies of React. Your bundle includes two or more separate copies of React. This happens in monorepos, linked packages, or misconfigured bundlers.
- Breaking the Rules of Hooks. You are calling a hook outside a function component — in a regular function, class component, event handler, or at the module level.
Version History That Changes the Failure Mode
The mechanics behind this error — and the patterns that trigger it — have shifted significantly across React versions. Match your symptom to the right era before applying a fix.
- React 16.8 (Feb 6, 2019) introduced hooks. Before 16.8, the error literally cannot occur because hooks did not exist. Code bases stuck on 16.7 or earlier hit the related “Cannot use hooks in this version” message instead.
- React 16.13 (Mar 2020) added warnings for setState during render and stricter detection of bad hook patterns. Some hook misuses that silently worked in 16.8 became hard errors here.
- React 17 (Oct 20, 2020) moved event delegation from
documentto the root container and introduced the new JSX transform (noimport React from "react"required). The new JSX transform changed how React is imported in compiled output; misconfigured Babel/SWC presets started picking up a different React instance, producing duplicate-React errors in projects that had been fine on 16. - React 18 (Mar 29, 2022) introduced automatic batching, concurrent rendering,
useId,useTransition,useSyncExternalStore, and<StrictMode>double-invocation of components, effects, and state initializers in development. Strict Mode double-render itself does NOT cause this error, but custom hooks with side effects in the body now run twice in dev — bugs that were hidden by single rendering become visible. - React 18 also requires
createRootinstead ofReactDOM.render. CallingReactDOM.renderin a 16/17 way works but emits a warning; mixing root APIs across micro-frontends can yield two live React instances. - React 18.3 (Apr 2024) added deprecation warnings for everything removed in React 19. If you see
react.createRoot is deprecatedstyle warnings, you are reading 18.3 output. - React 19 (stable Dec 5, 2024) stabilized the
use()hook, Actions, theuseActionState/useFormStatushooks, ref-as-prop (no moreforwardReffor most components), and the new<title>/<meta>/<link>hoisting. Theuse()hook is the first hook that can be called conditionally — inside a branch of anif, even — because it suspends rather than relies on hook order. This relaxes Fix 4’s rule, but only foruse(), not foruseState/useEffect. - React Compiler RC (May 2025) auto-memoizes components and tracks hook calls statically. With the compiler on, many hook-rule violations are caught at build time instead of runtime, but you can also encounter new errors if the compiler decides your code violates assumptions (mutation of props, mutation of state).
- Server Components stabilized in Next.js 13 (Oct 2022) with the App Router. Server Components run on the server with no React dispatcher mounted for hooks; calling
useStatein a server component throws this exact error. The fix is"use client"at the top of the file (Fix 8). - Server Components in Remix (renamed React Router 7, Nov 2024) and other frameworks (TanStack Start, Waku) all enforce the same constraint.
If you are still on React 16.8–17, upgrading to 18.3 or 19 changes both the error messages you see and the diagnostic tooling (React.captureOwnerStack, the React DevTools profiler) available to you.
Fix 1: Check for Mismatched React Versions
Verify that react and react-dom are the same version:
npm ls react react-domYou should see the same version for both:
├── [email protected]
└── [email protected]If the versions differ, fix them:
npm install [email protected] [email protected]Or with the latest:
npm install react@latest react-dom@latestAlso check for react-test-renderer and @testing-library/react — they need to match your React version too.
Pro Tip: Pin
reactandreact-domto the exact same version inpackage.jsonto prevent npm from resolving them to different patch versions. Use exact versions ("react": "18.3.1") or the--save-exactflag.
Fix 2: Fix Multiple Copies of React
This is the most common cause, especially in monorepos and projects with linked packages. Two copies of React means two separate dispatchers, and hooks from one copy do not work with components from the other.
Detect duplicates:
npm ls reactIf you see React listed more than once at different paths, you have duplicates:
├── [email protected]
└─┬ [email protected]
└── [email protected] <-- duplicate!Fix: Make the library use your app’s React (peer dependency):
In the component library’s package.json, React should be a peerDependency, not a dependency:
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}Fix: Force a single copy with npm overrides:
In your app’s package.json:
{
"overrides": {
"react": "$react",
"react-dom": "$react-dom"
}
}This forces all packages to use your app’s version of React. For yarn, use resolutions instead. See Fix: npm ERESOLVE unable to resolve dependency tree for more on resolving version conflicts.
Fix: Webpack alias to force single React:
In webpack.config.js:
const path = require("path");
module.exports = {
resolve: {
alias: {
react: path.resolve("./node_modules/react"),
"react-dom": path.resolve("./node_modules/react-dom"),
},
},
};Fix: Vite alias:
In vite.config.js:
import { defineConfig } from "vite";
import path from "path";
export default defineConfig({
resolve: {
alias: {
react: path.resolve("./node_modules/react"),
"react-dom": path.resolve("./node_modules/react-dom"),
},
},
});If Vite-specific import resolution surfaces alongside this error, double-check the alias path resolves to the project’s local node_modules/react rather than a hoisted one.
Fix: npm link issues:
If you are developing a package locally with npm link, the linked package uses its own node_modules/react instead of your app’s. Fix it by linking React back:
cd my-component-library
npm link ../my-app/node_modules/reactOr use npm install with a local path instead of npm link:
npm install ../my-component-libraryFix 3: Do Not Call Hooks Outside Function Components
Hooks must be called directly inside a React function component or a custom hook. They cannot be called in:
- Regular JavaScript functions
- Class components
- Event handlers (outside the component body)
- Callbacks (setTimeout, Promise.then)
- Module-level code
Broken — hook in a regular function:
// This is NOT a component — it doesn't return JSX
function getUser() {
const [user, setUser] = useState(null); // Invalid hook call
return user;
}Fixed — make it a component or custom hook:
// Option 1: Custom hook (must start with "use")
function useUser() {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(setUser);
}, []);
return user;
}
// Option 2: Use the custom hook inside a component
function UserProfile() {
const user = useUser();
return <div>{user?.name}</div>;
}Broken — hook in a class component:
class MyComponent extends React.Component {
render() {
const [count, setCount] = useState(0); // Invalid hook call
return <p>{count}</p>;
}
}Fixed — convert to a function component:
function MyComponent() {
const [count, setCount] = useState(0);
return <p>{count}</p>;
}Class components cannot use hooks. If you cannot convert the entire class, extract the hook logic into a function component and compose them.
Fix 4: Do Not Call Hooks Conditionally
Hooks must be called in the same order on every render. Conditional hook calls break React’s internal tracking:
Broken:
function Profile({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null); // Conditional hook!
}
return <div>Profile</div>;
}Fixed:
function Profile({ isLoggedIn }) {
const [user, setUser] = useState(null); // Always called
if (!isLoggedIn) {
return <div>Please log in</div>;
}
return <div>{user?.name}</div>;
}Call all hooks at the top level of the component, before any conditional returns. For more on this rule, see Fix: React Hooks called conditionally.
Fix 5: Fix Hooks in Event Handlers and Callbacks
You cannot call hooks inside event handlers, timeouts, or promise callbacks:
Broken:
function Counter() {
return (
<button onClick={() => {
const [count, setCount] = useState(0); // Invalid!
}}>
Click
</button>
);
}Fixed:
function Counter() {
const [count, setCount] = useState(0); // Correct — top level
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}The hook is declared at the component level. The event handler uses the setter function, not the hook itself.
Fix 6: Check Your Bundler Output
Sometimes the error appears only in production or after building. This can indicate a bundling issue where React is included multiple times.
Debug: Add this to your app’s entry point:
import React from "react";
import ReactDOM from "react-dom";
console.log("React version:", React.version);
console.log("ReactDOM version:", ReactDOM.version);
console.log("React === window.React:", React === window.React);If the versions differ or the identity check fails, you have duplicate React bundles.
Check your bundle for duplicates:
With webpack:
npx webpack --stats-modules-by-issuer | grep "react"With the webpack bundle analyzer:
npm install --save-dev webpack-bundle-analyzer
npx webpack --json > stats.json
npx webpack-bundle-analyzer stats.jsonIf you see multiple React chunks in the bundle visualization, fix your resolve aliases (see Fix 2).
Fix 7: Fix Hooks in React Native
React Native has the same hook rules as React, plus additional ways to encounter duplicates:
Metro bundler caching issues:
npx react-native start --reset-cacheClear all caches:
cd android && ./gradlew clean && cd ..
cd ios && pod install && cd ..
npx react-native start --reset-cacheCheck for duplicate React in React Native:
npm ls reactReact Native projects sometimes end up with React as both a direct dependency and a transitive dependency from react-native itself. Remove the duplicate and ensure only one copy exists.
Fix 8: Fix Hooks in Next.js
Next.js projects can hit this error in specific scenarios:
Server components vs. client components:
In the App Router, server components cannot use hooks. If you need hooks, add "use client" at the top of the file:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}Without "use client", Next.js treats the component as a server component where hooks are not available. If server/client rendering mismatches cause hydration issues, see Fix: Next.js hydration failed.
Importing from wrong package:
Make sure you import hooks from react, not from other packages that re-export them:
// Correct:
import { useState } from "react";
// Wrong (might pull a different React copy):
import { useState } from "some-library/react";Fix 9: Fix Hooks in Monorepos
Monorepos (Lerna, Turborepo, pnpm workspaces) are the most common source of duplicate React copies. Multiple packages can each install their own React.
Fix: Hoist React to the root:
In a pnpm workspace, add to .npmrc:
public-hoist-pattern[]=react
public-hoist-pattern[]=react-domIn a Yarn workspace, React is hoisted by default. If it is not, check for nohoist settings in package.json.
Fix: All packages should use peerDependencies for React:
Internal packages should never have react in dependencies. Always use peerDependencies:
{
"name": "@myorg/ui-components",
"peerDependencies": {
"react": "^18.0.0"
}
}Verify single copy after fixing:
find node_modules -name "react" -maxdepth 3 -type d | grep "/react$"You should see only one path. If you see multiple, the hoisting is not working correctly.
Still Not Working?
If none of the fixes above resolved the error:
Check custom renderers. If you use react-three-fiber, ink, or other custom React renderers, they need their own compatible React version. Ensure they do not bundle a separate React copy.
Check for stale caches. Delete node_modules and lock files, then reinstall:
rm -rf node_modules package-lock.json
npm installCheck for barrel file re-exports. If a shared package re-exports hooks, the re-export might resolve to a different React instance. Import directly from react instead.
Check for React in CDN scripts. If you load React via a <script> tag and via npm, you have two copies. Use one or the other, not both.
Check for incorrect component export. If you export a class or plain object instead of a function component, and then use hooks inside it, you get this error. Verify the component is a function:
// Wrong — exporting an object:
export default { MyComponent };
// Right — exporting the component:
export default MyComponent;Use the React DevTools profiler. Open the React DevTools Components tab. If it shows “No React detected,” your app might be using a different React instance than DevTools expects. This confirms a duplicate React issue.
Check package.json#exports field collisions. A library shipped with both module and exports fields can resolve differently in CJS vs ESM environments. The CJS side may pull React from a different path than the ESM side, producing two live React instances in a single page. Inspect the resolved path with require.resolve("react") and import.meta.resolve("react").
Check for HMR (hot module replacement) double-mount. A buggy HMR update can leave the old React instance running while React Fast Refresh wires up the new one. The first render after a hot reload throws Invalid hook call even though a full page reload works fine. The fix is a full reload after Fast Refresh failures, plus updating to the latest @vitejs/plugin-react or next/swc.
Check whether you imported a hook from a library subpath. import { useFormState } from "react-dom" works in React 19, but importing the same name from react-dom/server does not — it returns undefined, and calling undefined() produces “is not a function,” but if the library wraps the call it can surface as Invalid hook call. Verify import paths against the library’s current package.json#exports.
Check React 19 use() confusion. With React 19’s use() hook, you can call a hook conditionally, but only use() itself. If you wrap useState in an if thinking the new rules apply universally, you still trip this error. The relaxation is specific to use().
If the hook error leads to too many re-renders, you likely have a state setter being called directly in the render body instead of in an event handler or effect.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AutoAnimate Not Working — Transitions Not Playing, List Items Not Animating, or React State Changes Ignored
How to fix @formkit/auto-animate issues — parent ref setup, React useAutoAnimate hook, Vue directive, animation customization, disabling for specific elements, and framework integration.
Fix: Blurhash Not Working — Placeholder Not Rendering, Encoding Failing, or Colors Wrong
How to fix Blurhash image placeholder issues — encoding with Sharp, decoding in React, canvas rendering, Next.js image placeholders, CSS blur fallback, and performance optimization.
Fix: Docusaurus Not Working — Build Failing, Sidebar Not Showing, or Plugin Errors
How to fix Docusaurus issues — docs and blog configuration, sidebar generation, custom theme components, plugin setup, MDX compatibility, search integration, and deployment.
Fix: Embla Carousel Not Working — Slides Not Scrolling, Autoplay Not Starting, or Thumbnails Not Syncing
How to fix Embla Carousel issues — React setup, slide sizing, autoplay and navigation plugins, loop mode, thumbnail carousels, responsive breakpoints, and vertical scrolling.