「React has detected a change in the order of Hooks」警告の解決方法

intermediate⚛️ React2026-06-04| React 16.8+, Next.js, Vite, Create React App, Webブラウザ(Chrome、Firefoxなど)

Error Message

Warning: React has detected a change in the order of Hooks called by ComponentName. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
#react#hooks#rules-of-hooks#debugging#javascript

問題の概要コンソールに表示される大量の赤文字に頭を抱えていませんか?「React has detected a change in the order of Hooks」という警告は、単なる煩わしいメッセージではなく、データの破損を防ぐためのセーフガードです。このエラーは、あるレンダリングから次のレンダリングの間で、定義したフック(Hooks)の実行順序が変わったときに発生します。

Warning: React has detected a change in the order of Hooks called by MyComponent.

Reactは状態(state)を名前で追跡しているわけではありません。代わりに、ポインタシステムを使用しています。useStateuseEffectを呼び出すたびに、Reactは内部リストの各スロットに対して「カーソル」を移動させます。1回目のレンダリングで3つのフックを呼び出し、2回目で2つしか呼び出さなかった場合、カーソルの位置がずれてしまいます。これにより、「ユーザープロフィール」の状態が誤って「ダークモード」の切り替えスイッチに漏れ出し、予期しないクラッシュを引き起こす可能性があります。

よくある原因- フックを ifswitch 文で囲んでいる。- 早期リターン(ローディング中のガードなど)の後にフックを配置している。- フックを map()for ループの中にネストしている。- コンポーネントのルートではなく、ヘルパー関数の中でフックを定義している。## ステップバイステップの解決策### シナリオ1:ifブロック内のフック特定のIDが存在する場合にのみデータを取得しようとすることがあります。これは論理的に思えますが、そのIDがnullになった場合にReactの内部レジストリを壊してしまいます。

❌ アンチパターン:

function UserProfile({ userId }) {
  if (userId) {
    // ❌ エラー!Reactはこのフックを時々しか認識しません
    useEffect(() => {
      fetchUser(userId);
    }, [userId]);
  }

  return <div>ユーザープロフィール</div>;
}

✅ 正しい実装: フックはトップレベルに保ち、条件チェックを副作用(effect)のコールバック内に移動します。

function UserProfile({ userId }) {
  useEffect(() => {
    if (!userId) return; // ロジックはフックの中に記述します

    fetchUser(userId);
  }, [userId]);

  return <div>ユーザープロフィール</div>;
}

シナリオ2:早期リターンの後のフックローディングスピナーが原因でこの問題が発生することがよくあります。JSXを早期にリターンしてしまうと、その行より下に定義されたフックは実行されません。データ読み込み後にフックの数が0から5に変わると、クラッシュの原因になります。

❌ アンチパターン:

function Dashboard({ data, loading }) {
  if (loading) return <Spinner />;

  // ❌ エラー!このフックはローディングフェーズ中にスキップされます
  const [activeTab, setActiveTab] = useState(0);

  return <div>{data}</div>;
}

✅ 正しい実装: すべての状態(state)および副作用(effect)の宣言を、関数の最上部( if 文の前)に移動します。

function Dashboard({ data, loading }) {
  const [activeTab, setActiveTab] = useState(0); // ルート(最上部)で定義します

  if (loading) return <Spinner />;

  return <div>{data}</div>;
}

シナリオ3:ループ内のフック動的なリストは厄介です。今日10個のアイテムがあっても、明日8個になれば、Reactのメモリから2つのフック呼び出しが削除されたことになります。

❌ アンチパターン:

function ItemList({ items }) {
  return items.map(item => {
    const [status] = useState('保留中'); // ❌ エラー!リストの長さに応じてフックの数が変わってしまいます
    return <li>{item.name} - {status}</li>;
  });
}

✅ 正しい実装: アイテム固有の状態を独自のコンポーネントに移動します。各子コンポーネントが独立して自身のフックを管理するようにします。

function ListItem({ name }) {
  const [status] = useState('保留中');
  return <li>{name} - {status}</li>;
}

function ItemList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <ListItem key={item.id} name={item.name} />
      ))}
    </ul>
  );
}

ソリューションのテスト- コンソールをクリアする: ページを更新し、以前クラッシュを引き起こしたUI状態を再現します。赤い警告が表示されないことを確認してください。- 状態の持続性を検証する: タブやビューを切り替えてみてください。 useState の値が他の変数に「飛び火」していないか確認します。- Run ESLint: npm run lint を実行します。最近の多くの開発環境では、保存する前に react-hooks/rules-of-hooks 違反を検出してくれるため、1回のエラーにつき約15分のデバッグ時間を節約できます。## 予防のヒント- Strictモード: <React.StrictMode> を有効にしたままにしてください。開発環境では、コンポーネントを意図的に2回呼び出すことで、こうしたフックの順序に関するバグをあぶり出します。- 「トップレベル」のルール: コンポーネントを2つのパートに分けてイメージしてください。パート1は上部の「フックのセットアップ」、パート2は下部の「ロジックとレンダリング」です。これらを決して混ぜないようにしましょう。

Related Error Notes