できない.dev

React で「Invalid hook call」が解決できない

Hook をコンポーネント関数のトップレベル以外で呼んだ、または react が二重に読み込まれていると Invalid hook call が出る。
呼び出し位置と react / react-dom のバージョン整合を確認する。

公開: 更新:

実行例あり(2026-09-23 に実環境で検証)

要約

Invalid hook call. Hooks can only be called inside of the body of a function component は、Hook の呼び出し位置が不正 か react が二重に読み込まれている ときに出る。useState などはコンポーネント関数か use で始まるカスタム Hook の トップレベル でしか呼べない。
位置とバージョン整合の 2 点を順に潰す。

実行例

警告は考えられる原因を 3 つ並べて出すが、npm ls react が react を 1 つしか返していないため、重複ではなくコンポーネント外からの呼び出しだけが理由だと切り分けられる。
警告の直後に useState が null 参照で落ち、Hook をコンポーネントのトップレベルへ移した版では描画まで通っている。

$ node bad.js
Warning: 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
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
/tmp/tmp.HGbGhILyg2/node_modules/react/cjs/react.development.js:1622
  return dispatcher.useState(initialState);
                    ^
 
TypeError: Cannot read properties of null (reading 'useState')
    at Object.useState (/tmp/tmp.HGbGhILyg2/node_modules/react/cjs/react.development.js:1622:21)
    at readValue (/tmp/tmp.HGbGhILyg2/bad.js:6:21)
    at Object.<anonymous> (/tmp/tmp.HGbGhILyg2/bad.js:11:15)
    at Module._compile (node:internal/modules/cjs/loader:1521:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1623:10)
    at Module.load (node:internal/modules/cjs/loader:1266:32)
    at Module._load (node:internal/modules/cjs/loader:1091:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:164:12)
    at node:internal/main/run_main_module:28:49
 
Node.js v20.20.2
$ echo $?
1
$ node solve_toplevel.js
<p>0</p>
$ echo $?
0
$ npm ls react
tmp.hgbghilyg2@1.0.0 /tmp/tmp.HGbGhILyg2
+-- react-dom@18.3.1 extraneous
| `-- react@18.3.1 deduped
`-- react@18.3.1 extraneous
$ node -e "console.log(require('react/package.json').version, require('react-dom/package.json').version)"
18.3.1 18.3.1

— 2026-09-23 時点の出力

検証環境

検証日
実行環境
node:20Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. トップレベル以外で呼んでいる: 条件分岐・ループ・try、イベントハンドラやネストした関数の中で useState などを呼んでいる
  2. 対象外の関数から呼んでいる: 通常のユーティリティ関数やクラスコンポーネントから Hook を呼んでいる
  3. バージョン不一致: react と react-dom のメジャーが揃っていない
  4. react の重複: モノレポや npm link で react が 2 つ入り、Hook のディスパッチャが分裂している

解決策

1. トップレベルで呼ぶ

// NG: 条件の内側で呼んでいる
function Comp({ on }: { on: boolean }) {
  if (on) {
    const [v, setV] = useState(0);
  }
  return null;
}
 
// OK: 先に Hook、分岐はその後
function Comp({ on }: { on: boolean }) {
  const [v, setV] = useState(0);
  return on ? <span>{v}</span> : null;
}

呼び出し順を毎回同じにするのが Rules of Hooks(新しいタブで開く) の原則。

2. 呼べる場所か確認する

Hook を使ってよいのは関数コンポーネント本体か、名前が use で始まるカスタム Hook の中だけ。
それ以外の関数からロジックを切り出すときは、カスタム Hook 化する。

3. バージョンを揃える

npm ls react react-dom

react と react-dom を同一メジャーに合わせる。
片方だけ更新した状態が典型的な不一致。

4. react の重複を解消する

npm dedupe
npm ls react      # 1 つだけになっているか確認

それでも 2 つ残るなら、bundler 側で react を単一パスに alias する。
重複時の詳しい対処は Invalid hook call 警告の公式解説(新しいタブで開く) に整理されている。

この記事は役立ちましたか?