Reactのカスタムフックでリトライ処理を組み込む方法!初心者でもわかるAPI再試行の基本
生徒
「ReactでAPIを呼び出したときに失敗したら、もう一度やり直すことはできますか?」
先生
「はい、できますよ。その仕組みを『リトライ処理』といいます。」
生徒
「リトライ処理ってどういうときに使うんですか?」
先生
「例えば、インターネットが一瞬切れただけでエラーになるのは不便ですよね。そんなとき自動で再試行できれば、ユーザーは快適に使えます。今日はその方法をカスタムフックを使って学びましょう。」
1. リトライ処理とは?
リトライ処理とは、APIの通信に失敗したときに、一定回数や一定時間をおいて再試行する仕組みのことです。たとえば、買い物アプリで商品一覧を取得するときに一瞬通信が途切れても、もう一度試すことで正常に表示できることがあります。
これは日常生活に例えると、友達に電話をかけたときに「圏外です」と出ても、もう一度かけ直すと繋がることがあるのと同じです。
2. Reactでリトライ処理が必要な理由
インターネット通信は常に安定しているわけではありません。地下鉄や電波の弱い場所では簡単にエラーになります。そのたびにユーザーに「失敗しました」と出すだけでは不親切です。
そこで、Reactのアプリにリトライ処理を組み込むことで「一度の失敗では諦めずに再挑戦」できるようにします。これによりユーザー体験が大きく改善され、アプリが「頼れる存在」になります。
3. カスタムフックでリトライ処理を作る基本
まずは基本的なカスタムフックを作って、失敗したときに数回だけ自動的にリトライするようにしましょう。
import { useState, useEffect } from "react";
function useFetchWithRetry(url, options = {}, retries = 3, delay = 1000) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
let attempt = 0;
const fetchData = () => {
fetch(url, options)
.then((res) => {
if (!res.ok) {
throw new Error("サーバーエラーが発生しました");
}
return res.json();
})
.then((json) => setData(json))
.catch((err) => {
if (attempt < retries) {
attempt++;
setTimeout(fetchData, delay);
} else {
setError(err.message);
}
});
};
fetchData();
}, [url]);
return { data, error };
}
export default useFetchWithRetry;
このカスタムフックでは、retriesでリトライ回数を指定し、delayでリトライ間隔を設定しています。例えば3回まで1秒間隔で再試行する仕組みです。
4. 作ったリトライ付きカスタムフックを使う
次に、このカスタムフックを実際にアプリで使ってみましょう。
import React from "react";
import useFetchWithRetry from "./useFetchWithRetry";
function App() {
const { data, error } = useFetchWithRetry(
"https://api.example.com/items",
{},
3,
2000
);
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. リトライ処理の工夫
リトライ処理は便利ですが、無限に繰り返すとサーバーやユーザーに負担をかけます。そのため「回数を制限する」「間隔を少しずつ長くする」といった工夫が必要です。
例えば、最初は1秒後に再試行し、次は2秒、次は4秒と倍にしていく方法を「エクスポネンシャルバックオフ」と呼びます。これは通信が不安定なときに特によく使われる考え方です。
6. リトライ処理を導入するメリット
リトライ処理をカスタムフックとして導入することで、次のようなメリットがあります。
- ユーザーが気づかないうちに通信の一時的な失敗を解決できる
- コードの中で同じエラーハンドリングを何度も書かなくて済む
- アプリ全体の信頼性が高まり、安心して使えるサービスになる
Reactのカスタムフックを使えば、こうした仕組みを簡単に再利用できるのが魅力です。
まとめ
ここまで、Reactのカスタムフックを利用したAPIのリトライ処理について詳しく解説してきました。フロントエンド開発において、外部APIとの通信は避けて通れない要素です。しかし、ネットワークの不安定さやサーバーの一時的な過負荷など、開発者の意図しない場所でエラーが発生することは珍しくありません。そこで重要になるのが、今回学んだ「自動再試行(リトライ)」の考え方です。
リトライ処理の実装で意識すべきポイント
単に「失敗したらもう一度実行する」というだけでなく、実際の運用では以下の3つのポイントを意識することが、より質の高いアプリケーション開発に繋がります。
- リトライ回数の上限設定: 無限にリトライを繰り返すと、ブラウザのメモリを消費し続けたり、サーバー側に過度な負荷(DoS攻撃のような状態)を与えてしまったりするリスクがあります。必ず3回〜5回といった適切な上限を設けましょう。
- 待機時間(ディレイ)の調整: 失敗した直後に即座にリトライすると、サーバーが回復していない可能性が高いです。数秒のバッファを持たせることで、成功率を高めることができます。
- ユーザーへのフィードバック: リトライ中であることをユーザーに伝えるUI(「再試行しています...」といったメッセージやスピナーの表示)を検討することで、ユーザーの不安を解消できます。
応用編:さらに高度なリトライ処理(TypeScript版)
実務では型安全性を確保するために、TypeScript(TSX)を使ってカスタムフックを定義することが一般的です。また、リトライの回数や間隔を呼び出し側から自由に制御できるように、オプションとして受け取る形にすると汎用性が高まります。
import { useState, useCallback } from "react";
interface RetryOptions {
maxRetries?: number;
delayMs?: number;
}
function useApiWithRetry() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [data, setData] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async (url: string, options: RetryOptions = {}) => {
const { maxRetries = 3, delayMs = 1500 } = options;
let lastError: any;
setIsLoading(true);
setError(null);
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`ステータスコード: ${response.status}`);
}
const result = await response.json();
setData(result);
setIsLoading(false);
return; // 成功したら終了
} catch (err: any) {
lastError = err;
console.log(`${i + 1}回目の試行に失敗しました。再試行します...`);
if (i < maxRetries - 1) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
}
// すべてのリトライが失敗した場合
setError(lastError?.message || "データの取得に失敗しました");
setIsLoading(false);
}, []);
return { data, isLoading, error, fetchData };
}
export default useApiWithRetry;
上記のコードでは、forループとasync/awaitを組み合わせることで、非同期処理の流れを直感的に記述しています。useCallbackを使っているため、親コンポーネントが再レンダリングされても関数が不必要に生成されず、パフォーマンス面でも優れています。
Next.jsでの利用例
Next.js(App RouterやPages Router)を利用している場合でも、クライアントコンポーネント内でこのカスタムフックを活用できます。以下は、ボタンをクリックしたタイミングでリトライ機能付きのデータ取得を開始する実装例です。
"use client";
import React from "react";
import useApiWithRetry from "../hooks/useApiWithRetry";
export default function ProductPage() {
const { data, isLoading, error, fetchData } = useApiWithRetry();
const handleLoadData = () => {
fetchData("https://api.example.com/products", { maxRetries: 5, delayMs: 2000 });
};
return (
<div className="p-5">
<h2 className="text-xl font-bold mb-4">製品データ管理</h2>
<button
onClick={handleLoadData}
className="btn btn-primary"
disabled={isLoading}
>
{isLoading ? "読み込み中..." : "データを取得する"}
</button>
{error && (
<div className="alert alert-danger mt-3">
エラー: {error} (通信環境を確認してください)
</div>
)}
{data && (
<div className="mt-4">
<h3>取得結果:</h3>
<pre className="bg-light p-3">{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</div>
);
}
APIリトライの仕組みを導入することは、単なるエラー対策以上の価値があります。それは「ユーザーにストレスを与えないための配慮」です。ちょっとした電波の瞬断で「エラー画面」を見せられるのと、裏側でひっそり解決して「正しい画面」を見せられるのとでは、サービスの信頼感に大きな差が生まれます。 Reactの強力な機能であるカスタムフックを使いこなし、どんな環境でも安定して動作するタフなアプリケーションを目指しましょう。
先生
「さて、カスタムフックを使ったリトライ処理の作り方を一通り見てきましたが、どうでしたか?」
生徒
「はい!今まではエラーが出たらすぐに『失敗しました』と出すしかないと思っていました。でも、コードを工夫するだけで、自動で何度も試してくれるなんて驚きです。まるで執念深いロボットが裏で頑張ってくれているみたいですね!」
先生
「ふふふ、執念深いというよりは、粘り強いと言ってあげてください。特に地下鉄やカフェのWi-Fiを使っているユーザーにとって、この数秒のリトライがどれだけ助かるか。想像してみてください。」
生徒
「確かに。僕もスマホで見ていて急にエラーになると、ページをリロードするのが面倒でアプリを閉じちゃうことがあります。自動でやってくれれば、そのまま待っていればいいだけですもんね。」
先生
「その通りです。ただ、最後の方で紹介した『エクスポネンシャルバックオフ』のように、回数を重ねるごとに待ち時間を増やすといった優しさも忘れないでくださいね。サーバー側にも事情がありますから。」
生徒
「相手(サーバー)のことも考えるのが、良いエンジニアの条件なんですね。TypeScriptでの書き方も勉強になったので、さっそく自分のポートフォリオサイトにも組み込んでみます!」
先生
「素晴らしい意気込みです。カスタムフック化しておけば、他のプロジェクトでもそのまま使い回せる『秘伝のタレ』になりますよ。ぜひ自分なりにアレンジしてみてください!」