Skip to content

Fix: Invalid hook call. Hooks can only be called inside of the body of a function component

FixDevs ·

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 three causes listed in the error message cover most scenarios:

  • Mismatched versions. react and react-dom are at different major versions. For example, react@18 with react-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.

Fix 1: Check for Mismatched React Versions

Verify that react and react-dom are the same version:

npm ls react react-dom

You should see the same version for both:

├── react@18.3.1
└── react-dom@18.3.1

If the versions differ, fix them:

npm install react@18.3.1 react-dom@18.3.1

Or with the latest:

npm install react@latest react-dom@latest

Also check for react-test-renderer and @testing-library/react — they need to match your React version too.

Pro Tip: Pin react and react-dom to the exact same version in package.json to prevent npm from resolving them to different patch versions. Use exact versions ("react": "18.3.1") or the --save-exact flag.

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 react

If you see React listed more than once at different paths, you have duplicates:

├── react@18.3.1
└─┬ my-component-library@1.0.0
   └── react@18.2.0    <-- 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"),
    },
  },
});

For Vite-specific import resolution issues, see Fix: Vite failed to resolve import.

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/react

Or use npm install with a local path instead of npm link:

npm install ../my-component-library

Fix 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.json

If you see multiple React chunks in the bundle visualization, fix your resolve aliases (see Fix 2).

For module resolution errors in general, see Fix: Module not found: Can’t resolve.

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-cache

Clear all caches:

cd android && ./gradlew clean && cd ..
cd ios && pod install && cd ..
npx react-native start --reset-cache

Check for duplicate React in React Native:

npm ls react

React 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-dom

In 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 install

Check 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.

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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles