Reactのカスタムフックでよくあるエラーと解決方法を初心者向けに解説
生徒
「先生、カスタムフックを作ったらエラーが出てしまいました。どうしたらいいですか?」
先生
「カスタムフックではよくあるエラーがいくつかあります。それぞれ原因と解決方法を順番に見ていきましょう。」
生徒
「エラーって怖いです…。初心者でもわかるように教えてください!」
先生
「もちろんです。まずはReactのルールを知ることが大切です。」
1. フックの呼び出しルール違反エラー
カスタムフックはReact関数コンポーネントまたは他のカスタムフックの中でしか呼び出せません。これを破ると「Hooks can only be called inside the body of a function component」というエラーが出ます。
例えば、普通の関数やイベントハンドラーの中で呼び出すとエラーになります。
function fetchData() {
const [data, setData] = useCustomHook(); // ❌エラーになる
}
解決方法は、フックを関数コンポーネント内またはカスタムフック内に移動させることです。
function App() {
const [data, setData] = useCustomHook(); // ✅正しい
}
2. 依存関係配列の指定ミスによる無限ループ
useEffectを使ったフックで依存関係配列を正しく指定しないと、レンダリングが無限に繰り返されることがあります。
useEffect(() => {
fetchData();
}); // ❌依存配列が空でないので毎回実行される
解決方法は、依存関係配列を正しく設定することです。配列に必要な変数を入れるか、空配列で初回のみ実行にします。
useEffect(() => {
fetchData();
}, []); // ✅初回だけ実行
3. 状態管理の初期化忘れ
useStateで初期値を設定しないと、undefinedを扱うエラーが出ることがあります。例えば、オブジェクトのプロパティにアクセスするとエラーになります。
const [user, setUser] = useState();
console.log(user.name); // ❌userがundefinedなのでエラー
解決方法は、初期値を空オブジェクトやnullで設定して、レンダリング時に安全に扱うことです。
const [user, setUser] = useState({ name: "" });
console.log(user.name); // ✅安全にアクセスできる
4. 非同期処理のタイミング問題
カスタムフック内で非同期処理を行う場合、コンポーネントがアンマウントされたあとに状態更新を行うとエラーが出ます。「Can't perform a React state update on an unmounted component」と表示されます。
解決方法は、フラグを使ってアンマウント後の更新を防ぐことです。
useEffect(() => {
let isMounted = true;
fetchData().then(result => {
if (isMounted) setData(result);
});
return () => { isMounted = false; };
}, []);
5. フックのネーミングやファイル管理の注意
カスタムフックは必ずuseから始まる名前にする必要があります。例えば、useCounterやuseFetchなどです。これを守らないと、Reactがフックとして認識せず、意図しない動作になります。
また、フックごとにファイルを分け、再利用性を高めるとエラーの原因を特定しやすくなります。
6. まとめに変わる考え方
Reactのカスタムフックでよくあるエラーは、呼び出しルール、依存関係配列、状態初期化、非同期処理、ネーミングの順守などがポイントです。これらを意識すれば、エラーを防ぎ、効率的にフックを作成できます。
まとめ
Reactのカスタムフック開発において、エラーに直面することは決して珍しいことではありません。むしろ、エラーは自分のコードをより堅牢にするための貴重なフィードバックです。今回ご紹介した「フックの呼び出しルール」「依存関係の管理」「初期値の設定」「非同期処理のクリーンアップ」「命名規則」という5つのポイントは、Reactエンジニアが日常的に意識すべき極めて重要な要素です。
特にカスタムフックは、ロジックを共通化してアプリケーション全体の可読性を高めるための強力なツールです。しかし、その強力さゆえに、一箇所でのミスが複数のコンポーネントに影響を及ぼす可能性もあります。まずは基礎的なルールを徹底し、小さなフックから作り始めて徐々に複雑なロジックへとステップアップしていくのが、上達への近道と言えるでしょう。
実践的なカスタムフックの例:useCounter
ここで、これまでの教訓を活かしたシンプルなカスタムフックの例を振り返ってみましょう。状態管理の初期化や、名前の付け方、関数コンポーネントでの呼び出し方を再確認してください。
import { useState, useCallback } from "react";
/**
* 数値をカウントアップ・ダウンするカスタムフック
* 初期値を引数として受け取り、安全に状態を管理します
*/
export const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
// 関数の再生成を抑えるためにuseCallbackを使用
const increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
const decrement = useCallback(() => {
setCount((prev) => prev - 1);
}, []);
const reset = useCallback(() => {
setCount(initialValue);
}, [initialValue]);
return { count, increment, decrement, reset };
};
このカスタムフックをコンポーネントで利用する
作成したカスタムフックを実際の画面で使ってみます。Reactのルール通り、関数コンポーネントのトップレベルで呼び出している点に注目してください。
import React from "react";
import { useCounter } from "./useCounter";
function CounterDisplay() {
// カスタムフックを呼び出す(useから始まる命名を守っています)
const { count, increment, decrement, reset } = useCounter(10);
return (
<div className="container mt-4">
<h3>現在のカウント: {count}</h3>
<div className="btn-group">
<button className="btn btn-primary" onClick={increment}>増加</button>
<button className="btn btn-danger" onClick={decrement}>減少</button>
<button className="btn btn-secondary" onClick={reset}>リセット</button>
</div>
</div>
);
}
export default CounterDisplay;
このように、ロジックをカスタムフック(useCounter)に切り出すことで、UIを担当するコンポーネント(CounterDisplay)が非常にスッキリします。エラーを恐れず、どんどん「自分だけの便利なフック」を作ってみてください。デバッグの際は、ブラウザのコンソールに出るエラーメッセージをよく読み、それが「ルールの違反」なのか「ロジックのミス」なのかを冷静に判断することが解決への近道です。
最後に、Reactの公式ドキュメントや最新のベストプラクティスを定期的にチェックすることも忘れずに。技術の進化は早いですが、今回学んだような「Hooksの基本原則」は、長く使える一生モノのスキルになります。
生徒
「先生、ありがとうございました!カスタムフックのエラーって、実は自分がReactの基本的なルールをうっかり忘れている時に起きることが多いんですね。」
先生
「その通りです。特に『フックはループや条件分岐の中で呼んではいけない』というルールや、依存関係配列の指定ミスは、ベテランでも時々やってしまうミスなんですよ。エラーが出た時は『Reactが正しく動こうとして教えてくれているんだ』とポジティブに捉えましょう。」
生徒
「なるほど。さっきのuseCounterの例を見て思ったのですが、useCallbackを使っているのは、パフォーマンスを意識してのことですか?」
先生
「鋭いですね!カスタムフックから関数を返す場合、その関数が他のuseEffectの依存関係に含まれることもあるので、メモ化しておくのが安全なんです。これをしないと、意図しない再レンダリングや無限ループの原因になることもありますからね。」
生徒
「深いですね……。でも、まずは『use』から名前を始めて、コンポーネントの先頭で呼ぶ、という基本から徹底してみます!非同期処理のクリーンアップも、useEffectの戻り値で関数を返す(isMountedを使う)方法がわかったので、もう怖くありません。」
先生
「素晴らしい意気込みです。もし実行結果が思うようにいかない時は、さっきのalertで解説したような動作イメージを頭の中でシミュレーションしてみてください。Reactのステート更新がどのタイミングで起きているかを理解すれば、デバッグ能力は飛躍的に向上しますよ。」
生徒
「はい!自分でも色々な機能を持ったカスタムフックを作って、共通化のメリットを実感してみます。また分からないことがあったら教えてください!」
先生
「もちろんです。試行錯誤を繰り返すことで、コードはどんどん綺麗になっていきます。頑張りましょうね!」