Reactのカスタムフックでエラーハンドリングを行う方法!初心者でもわかるReact Hooks入門
生徒
「ReactでAPIを呼び出したときにエラーが出た場合、どう処理すれば良いですか?」
先生
「それには『カスタムフック』を使ってエラーハンドリングを共通化する方法があります。」
生徒
「カスタムフックを使うと、毎回エラー処理を書く必要がなくなるんですか?」
先生
「その通りです。では、具体的に作り方を見ていきましょう。」
1. カスタムフックでエラーハンドリングをする理由
通常、Reactコンポーネント内でAPIを呼び出す場合、fetchやaxiosを使い、try-catchでエラーを処理します。しかし、複数のコンポーネントで同じ処理を書くのは面倒です。
そこで「カスタムフック」を使えば、エラー処理のロジックを一箇所にまとめて再利用できます。
カスタムフックを作ると、エラーが発生したときに共通の処理を行うことができ、コードがすっきり整理されます。
2. エラーハンドリング用のカスタムフックを作る
まず、API呼び出しや非同期処理で使える汎用的なカスタムフックを作ります。
import { useState } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("ネットワークに問題が発生しました");
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return { data, loading, error, fetchData };
}
export default useFetch;
このフックでは、dataに取得データ、loadingで読み込み中の状態、errorでエラー内容を管理しています。
fetchData関数を呼ぶと非同期処理が走り、エラーがあればerrorにセットされます。
3. カスタムフックを使ったコンポーネント例
次に、このuseFetchフックを使って実際にデータを取得し、エラー処理を表示する例です。
import React, { useEffect } from "react";
import useFetch from "./useFetch";
function UserList() {
const { data, loading, error, fetchData } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
useEffect(() => {
fetchData();
}, []);
if (loading) return <p>読み込み中です...</p>;
if (error) return <p>エラーが発生しました: {error}</p>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UserList;
このように、useFetchを使うだけで、データ取得とエラー処理が簡単に共通化できます。
4. エラーメッセージをカスタマイズする
カスタムフックでは、エラーメッセージを自由に変更することも可能です。
例えば、サーバーエラーやネットワークエラーで別々のメッセージを表示することができます。
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`サーバーエラー: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name === "TypeError") {
setError("ネットワークに接続できません");
} else {
setError(err.message);
}
}
このように、エラーの種類ごとに処理を分けることも可能です。
5. ポイント整理
カスタムフックでエラーハンドリングを行うことで、複数のコンポーネントで同じ処理を簡単に再利用できます。
非同期処理の成功・失敗・読み込み中の状態を管理することで、ユーザーにわかりやすいUIを提供することができます。
エラーの種類に応じてメッセージを分けることもできるので、実際の開発現場でも便利な設計です。
まとめ
Reactでカスタムフックを使ってエラーハンドリングを共通化する考え方は、はじめて非同期通信やAPI連携に取り組む学習者にとっても、その後の実務的な開発にとっても大きな意味を持ちます。ひとつひとつのコンポーネントの中にその都度同じようなエラー処理を書いてしまうと、コードの量が増えて読みづらくなるだけでなく、あとから挙動を変更したいときに修正漏れが生まれやすくなります。今回の記事で触れたように、カスタムフックという仕組みを使って「データ取得」「読み込み中の状態管理」「エラーメッセージの管理」をひとまとめにしておくことで、Reactのコンポーネント側は画面表示のことだけに集中できるようになり、役割分担がとてもはっきりします。これはReact Hooksの考え方にもよくなじむ設計であり、useStateやuseEffectといった基本フックと組み合わせることで、シンプルで分かりやすいエラーハンドリングを実現できます。 また、カスタムフックでエラーハンドリングを行うと、ユーザーインターフェースの一貫性という点でも大きなメリットがあります。たとえば同じアプリケーションの中に複数の一覧画面や詳細画面が存在し、それぞれが別々のAPIを呼び出している場合でも、共通のカスタムフックを通じて非同期処理を行えば、「読み込み中はどの画面でも同じメッセージを出す」「エラー時の文言を統一する」といった工夫が自然とできるようになります。これにより利用者は画面ごとに違う表現に迷わされることなく、直感的に状況を理解できるようになります。Reactのコンポーネント設計とカスタムフックの組み合わせは、見た目だけでなく体験全体の質を高めるうえでも欠かせない要素だと言えるでしょう。 さらに、エラーハンドリングをカスタムフックに閉じ込めておくことで、あとから「サーバーのステータスコードによってメッセージを出し分ける」「ネットワークエラーのときだけ再試行ボタンを表示する」「特定のエラー内容をログとしてどこかに送信する」といった拡張もしやすくなります。React Hooksを使った関数型の書き方は、処理の流れが素直で追いかけやすいため、将来的に要件が増えたときにも落ち着いて対応しやすい構造になります。こうした観点からも、カスタムフックによるエラーハンドリングは、学習段階から意識して身につけておきたい重要なパターンだと言えるでしょう。
サンプルプログラム:エラー状態と再読み込みをまとめたカスタムフック
import { useState, useCallback } from "react";
function useApiRequest(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const run = useCallback(async () => {
setLoading(true);
setError(null);
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error("サーバーから正しい応答がありませんでした");
}
const json = await res.json();
setData(json);
} catch (e) {
if (e.name === "TypeError") {
setError("ネットワークに接続できませんでした");
} else {
setError(e.message);
}
setData(null);
} finally {
setLoading(false);
}
}, [url]);
return { data, loading, error, run };
}
export default useApiRequest;
上記のようなカスタムフックを用意しておくと、Reactコンポーネント側では「どのタイミングでデータを取りに行くか」「取得した結果をどのように表示するか」という点にだけ集中できます。実際にこのフックを使うコンポーネントの例も見てみましょう。読み込み中の状態とエラー表示、そして再読み込みボタンまでをシンプルにまとめた構成です。
import React, { useEffect } from "react";
import useApiRequest from "./useApiRequest";
function PostList() {
const { data, loading, error, run } = useApiRequest(
"https://jsonplaceholder.typicode.com/posts"
);
useEffect(() => {
run();
}, [run]);
if (loading) {
return <div className="p-3">読み込み中です…</div>;
}
if (error) {
return (
<div className="card p-3">
<p>エラーが発生しました:{error}</p>
<button className="btn btn-primary mt-2" onClick={run}>
もう一度試す
</button>
</div>
);
}
if (!data) {
return <div className="p-3">データがありません</div>;
}
return (
<ul className="list-group">
{data.slice(0, 5).map((post) => (
<li className="list-group-item" key={post.id}>
{post.title}
</li>
))}
</ul>
);
}
export default PostList;
この例では、コンポーネントはloadingとerrorとdataという三つの状態を見て、どの表示に切り替えるかを決めているだけです。非同期処理の詳細やエラーハンドリングの細かな分岐はすべてカスタムフックの中に閉じ込められており、画面側のロジックはとても読みやすくなっています。Reactのカスタムフックを活用することで、API呼び出しやエラーハンドリングをどの画面でも同じ使い勝手で扱えるようになり、アプリ全体の見通しもぐっと良くなります。エラーが起きたときに落ち着いて状況を伝え、必要であれば再読み込みの手段も用意する、こうした丁寧な作りは、利用する人にとっても開発する側にとっても安心できる設計につながっていきます。
生徒
「きょう学んだカスタムフックのエラーハンドリングは、Reactの画面づくりにすごく役立ちそうだと感じました。ひとつの場所で非同期処理とエラーをまとめて管理できるのがとても便利ですね。」
先生
「その気づきはとても大事ですね。毎回同じようなエラー処理をコンポーネントごとに書いていると、修正も大変になりますし、挙動の違いも生まれやすくなります。カスタムフックにまとめることで、React Hooksの強みをしっかり生かした設計になりますよ。」
生徒
「データ取得中は読み込み中の表示、失敗したときはエラーメッセージ、成功したら一覧を表示、という流れもイメージしやすくなりました。エラーの内容を分けてメッセージを変えられるところも、実際のサービスっぽい感じがしました。」
先生
「実際の開発でも、利用者にとってわかりやすいメッセージを出すことはとても大切です。サーバー側の問題なのか、ネットワークの問題なのか、あるいは入力内容に問題があるのかなど、Reactの画面で丁寧に伝えられると信頼感も高まりますね。」
生徒
「これからはAPIを呼び出すコンポーネントを作るときに、まずカスタムフックにできないか考えてみようと思います。同じ書き方で使いまわせるようにしておくと、あとから機能追加するときも楽になりそうです。」
先生
「とても良い姿勢ですね。Reactのカスタムフックは、エラーハンドリングに限らず、認証やフォームの状態管理などさまざまな場面に応用できます。今回学んだエラーハンドリングの考え方を土台にして、いろいろな処理を整理しながら実装してみてください。」