問題の概要コンソールに表示される大量の赤文字に頭を抱えていませんか?「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)を名前で追跡しているわけではありません。代わりに、ポインタシステムを使用しています。useStateやuseEffectを呼び出すたびに、Reactは内部リストの各スロットに対して「カーソル」を移動させます。1回目のレンダリングで3つのフックを呼び出し、2回目で2つしか呼び出さなかった場合、カーソルの位置がずれてしまいます。これにより、「ユーザープロフィール」の状態が誤って「ダークモード」の切り替えスイッチに漏れ出し、予期しないクラッシュを引き起こす可能性があります。
よくある原因- フックを if や switch 文で囲んでいる。- 早期リターン(ローディング中のガードなど)の後にフックを配置している。- フックを 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>
);
}

