Reactのカスタムフックでエラー状態を管理する方法!初心者でもわかるAPIエラーハンドリング
生徒
「ReactでAPIを呼び出すときに、失敗したらどうなるんですか?」
先生
「良い質問ですね。API通信は常に成功するとは限りません。サーバーが落ちていたり、ネットワークが不安定だったりすると失敗することがあります。」
生徒
「そういうときって、画面はどうなるんですか?」
先生
「何も準備していないとエラーが出ても真っ白になってしまいます。そこで『エラー状態』を管理して、ユーザーにわかりやすいメッセージを表示することが大事なんです。」
1. エラー状態とは?
エラー状態とは、APIからデータを取ってくるときに「失敗しました」とアプリが認識する状態のことです。例えば、天気予報アプリでデータが取得できないときに「データの取得に失敗しました」と表示されるのを見たことがあるでしょう。それがまさにエラー状態の管理です。
プログラミング未経験の方にわかりやすく例えると、郵便を頼んだのに住所が間違っていて届かないことがありますよね。そのときに「住所が違います」と通知してくれるのがエラー状態の役割です。
2. Reactでエラーを管理する必要性
ReactでAPIを使うとき、ただ結果だけを表示するのでは不十分です。なぜなら通信は失敗することがあるからです。もしエラーを無視してしまうと、ユーザーは「動かない」「壊れている」と感じてしまいます。
エラー状態をきちんと管理して「ネットワークエラーが発生しました」「もう一度試してください」と表示できれば、ユーザーは安心して使うことができます。これはアプリの信頼性を高めるためにとても重要なことです。
3. カスタムフックでエラーを管理する基本
毎回同じようにuseStateやuseEffectでエラー処理を書くのは大変です。そこで「カスタムフック」を作ってエラー処理をまとめてしまいましょう。そうすれば、再利用できて開発が楽になります。
import { useState, useEffect } from "react";
function useFetchWithError(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error("サーバーエラーが発生しました");
}
return res.json();
})
.then((json) => setData(json))
.catch((err) => setError(err.message));
}, [url]);
return { data, error };
}
export default useFetchWithError;
このカスタムフックでは、エラーが起きたときにsetErrorで状態を保存します。これで呼び出し元のコンポーネントから「エラーがあるかどうか」を判定できるようになります。
4. 作ったカスタムフックを使ってみる
次に、このカスタムフックを実際にコンポーネントで使ってみましょう。
import React from "react";
import useFetchWithError from "./useFetchWithError";
function App() {
const { data, error } = useFetchWithError("https://api.example.com/items");
if (error) return <p>エラーが発生しました: {error}</p>;
if (!data) return <p>データを取得中です...</p>;
return (
<div>
<h1>商品一覧</h1>
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
export default App;
5. エラー状態をわかりやすく伝える工夫
ただ「エラー」と出すだけでは不親切です。例えば「ネットワークに接続できません」「サーバーが混雑しています」など、状況に応じたメッセージを表示することでユーザーは次に何をすればいいか理解できます。
さらに、再読み込みボタンを用意して「もう一度試す」ことができるようにすると、使いやすさがぐんと上がります。これは初心者でもすぐに取り入れられる工夫です。
6. ローディングとエラーを組み合わせる
実際のアプリでは、データ取得中とエラー発生後を両方管理する必要があります。そこで、ローディングとエラーを一緒に扱えるカスタムフックを作ると便利です。
import { useState, useEffect } from "react";
function useFetchWithLoadingAndError(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error("エラーが発生しました");
}
return res.json();
})
.then((json) => {
setData(json);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
export default useFetchWithLoadingAndError;
これで「読み込み中」「成功」「失敗」をきちんと分けて表示できるようになります。実際のアプリ開発ではこの形がよく使われます。
7. エラー管理をするメリット
エラー状態をきちんと管理することで、ユーザーにとって安心できるアプリを作れます。もし「真っ白な画面」が出るだけなら、不安になってアプリを閉じてしまうかもしれません。
「エラーが発生しました」と表示されるだけで「自分の操作が間違っていない」と理解できるので、信頼感が高まります。Reactのカスタムフックを活用すれば、この仕組みを簡単に実装できるのです。
まとめ
ここまで、Reactのカスタムフックを使ったエラー状態の管理方法について詳しく解説してきました。Webアプリケーションにおいて、API通信は切っても切り離せない重要な要素ですが、それと同時に「通信の失敗」も避けては通れない現実です。フロントエンド開発者の役割は、単にデータを画面に表示するだけでなく、予期せぬエラーが発生した際にユーザーを迷わせないための「道しるべ」を作ることにあると言っても過言ではありません。
Reactにおけるエラーハンドリングの重要ポイント
今回の内容をおさらいすると、エラー管理において特に意識すべき点は以下の3つです。
- ユーザー体験(UX)の向上: エラー時に画面が真っ白になるのを防ぎ、何が起きたのかを正確に伝えます。
- コードの再利用性: カスタムフック(useFetch等)にロジックをまとめることで、複数のコンポーネントで同じエラー処理を使い回せます。
- 状態の明確化: 「ローディング中」「成功」「エラー」の3つの状態を定義することで、条件分岐がシンプルになり、バグの少ないコードになります。
実践的なカスタムフックの応用例
さらに実戦で役立つテクニックとして、エラーが発生した際にユーザーが自ら「再試行」できる仕組みを組み込んだコードを紹介します。このように、単にエラーを表示するだけでなく、次のアクションを促すのがモダンなフロントエンド開発のスタンダードです。
import { useState, useEffect, useCallback } from "react";
/**
* 再試行機能付きのカスタムフック
*/
function useFetchWithRetry(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [refetchIndex, setRefetchIndex] = useState(0);
// 再取得をトリガーする関数
const refetch = useCallback(() => {
setRefetchIndex((prev) => prev + 1);
}, []);
useEffect(() => {
let isMounted = true;
setLoading(true);
setError(null);
fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error(`エラーコード: ${res.status} - データの取得に失敗しました`);
}
return res.json();
})
.then((json) => {
if (isMounted) {
setData(json);
setLoading(false);
}
})
.catch((err) => {
if (isMounted) {
setError(err.message);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, [url, refetchIndex]);
return { data, loading, error, refetch };
}
export default useFetchWithRetry;
このフックを使えば、コンポーネント側で「リトライボタン」を簡単に実装できます。プログラムを組む際は、常に「もし失敗したらどうするか?」という守りの姿勢を持つことが、プロフェッショナルへの第一歩です。
SEOとアクセシビリティへの配慮
エラーメッセージを実装する際は、SEOやアクセシビリティの観点も忘れてはいけません。例えば、エラー文言には role="alert" を付与したり、検索エンジンがコンテンツの不備を正しく認識できるように適切なHTMLタグを選択したりすることが大切です。
JavaScriptやTypeScriptを駆使して、堅牢なアプリケーションを構築していきましょう。Next.jsなどのフレームワークを使用している場合でも、このカスタムフックの考え方は共通して活用できる強力な武器になります。
生徒
「先生、カスタムフックを使うとエラー処理がすごくスッキリしますね!今まではコンポーネントの中に useState がたくさん並んでいて、何が何だか分からなくなっていました。」
先生
「そうでしょう。ロジックを切り出すことで、コンポーネントは『見た目』に集中できるようになります。これがReactらしい、宣言的なプログラミングの大きなメリットなんですよ。」
生徒
「今回の refetch の仕組みも面白いです。ボタンを押すだけでデータをもう一回取りにいけるのは、ユーザーにとっても親切ですね。でも、エラーメッセージの内容って、どんな風に決めるのが一番いいんでしょうか?」
先生
「鋭いですね。理想的なのは、エラーの原因を切り分けることです。例えば『404』なら『ページが見つかりません』、『500』なら『サーバーが混み合っています』といった具合です。技術的な用語をそのまま出すのではなく、ユーザーが次に何をすべきかを伝える言葉選びを意識してみてください。」
生徒
「なるほど。ただエラーと言うんじゃなくて、『通信環境を確認してください』とか具体的なアドバイスを添えるイメージですね。さっそく自分のポートフォリオサイトにも導入してみます!」
先生
「その意気です。API通信があるところにエラー管理あり。どんな時でも安定して動くアプリを目指して、一歩ずつ進んでいきましょう。Reactのスキルが一段階上がった証拠ですよ。」