ReactのuseApiData:API通信のloading・error・再取得を管理する
API取得を独自Hookへ切り出し、読み込み・失敗・成功、中止、古い通信結果の抑止、再取得を実装します。
API取得を独自Hookへ切り出し、読み込み・失敗・成功、中止、古い通信結果の抑止、再取得を実装します。
useApiDataは何をまとめるか
GETの通信状態を返すカスタムフック
useApiData は自作のHookです。URLに応じたJSON取得と、loading・error・data・reload をまとめます。fetchJsonの記事の src/api/fetchJson.ts を先に用意してください。ViteのReact+TypeScript構成で動かす例です。
URL変更時には以前のリクエストを中止し、古い結果で表示を上書きしないようにします。Effectは外部との同期を担当し、終了時には対応する後始末を行います。ReactのuseEffect解説では依存関係やクリーンアップが説明されています。
src/hooks/useApiData.tsの実装
URLと再取得回数を依存値にする
import { useEffect, useState } from 'react';
import { fetchJson } from '../api/fetchJson';
type Result = {
url: string;
data: unknown;
error: string | null;
loading: boolean;
};
export function useApiData(url: string) {
const [version, setVersion] = useState(0);
const [result, setResult] = useState<Result>({
url, data: null, error: null, loading: true,
});
useEffect(() => {
const controller = new AbortController();
let active = true;
setResult({ url, data: null, error: null, loading: true });
fetchJson(url, controller.signal).then(
data => {
if (active) setResult({ url, data, error: null, loading: false });
},
cause => {
if (active) setResult({
url, data: null, loading: false,
error: cause instanceof Error ? cause.message : '取得に失敗しました',
});
},
);
return () => {
active = false;
controller.abort();
};
}, [url, version]);
const current = result.url === url ? result : {
url, data: null, error: null, loading: true,
};
function reload() { setVersion(value => value + 1); }
return { ...current, reload };
}active はそのEffectが現在も有効かを示し、後始末後の成功・失敗結果を無視します。中止だけに依存せず、state更新にも条件を付けています。さらに結果へURLを保存し、URL変更直後のレンダーで前のURLの内容を表示しないようにしています。URLは空文字列でない有効な取得先を渡してください。
コンポーネントから利用する
src/App.tsx
前の記事と同じ public/api/message.json を配置します。Hookの戻り値は未検証データなので、画面側で必要なフィールドを確認します。
import { useApiData } from './hooks/useApiData';
export default function App() {
const { data, error, loading, reload } = useApiData('/api/message.json');
const message = typeof data === 'object' && data !== null &&
'message' in data && typeof data.message === 'string'
? data.message : null;
return <main>
<h1>通信状態を管理する</h1>
<button type="button" onClick={reload} disabled={loading}>再取得</button>
{loading ? <p role="status">読み込み中</p>
: error ? <p role="alert">{error}</p>
: message === null ? <p role="alert">データ形式が不正です</p>
: <p>{message}</p>}
</main>;
}null は未取得・失敗でも使うため、値だけで成功を判定しません。表示はまず loading、次に error、最後にデータ内容の順で分けます。空のメッセージも正常な文字列として扱います。
競合・Strict Mode・再取得の確認
遅い通信を使って確認する
開発者ツールで通信速度を遅くし、取得中に対象コンポーネントを非表示にしてください。後始末によって中止が要求されます。URLをAからBへ変更した場合は、Aが遅れて完了してもBの結果を上書きしないことを確認します。Strict Modeの開発時の追加実行でも、開始と後始末を対にします。
小さなHookの限界を理解する
この例にはキャッシュ、重複リクエストの集約、リトライ、タイムアウト、SSRでの事前取得はありません。複数箇所で同じURLを使うと通信とstateも別です。要件が増えたらフレームワークの取得機能やデータ取得ライブラリを比較します。Effect内部からPOSTして購入や登録を実行せず、送信イベントの処理として設計します。
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。