3秒でわかる
データ取得とローディング・エラーの状態管理をひとまとめにした自作フック。各コンポーネントで同じ組み合わせを書き直さずに済みます。
もう少し詳しく
どういうものか
React のカスタムフックとして書かれる、データ取得のための定番パターンです。内部で useState と useEffect を組み合わせ、{ data, loading, error } の3つをまとめて返します。React が標準で提供する関数ではなく、各プロジェクトが自分で書くか、SWR や TanStack Query のようなライブラリで置き換えるものです。
呼び出し側は URL を渡すだけで、通信中なのか、失敗したのか、成功して値が来たのかを見て表示を切り替えられます。
なぜ必要か
素の useEffect で取得を書くと、同じ形のコードが画面の数だけ増えます。しかも増えるのは3行ではなく、ローディングの初期化、エラーの捕捉、後片付けまで含めた20行前後です。どこかで後片付けを書き忘れると、その画面だけ警告が出たり、古い結果で上書きされたりします。
1つのフックに寄せておけば、修正も改善も1か所に効きます。
具体例
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
<a href="/glossary/fetch-api" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">fetch</a>(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error("<a href="/glossary/http" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">HTTP</a> " + res.status);
return res.json();
})
.then(setData)
.catch((e) => {
if (e.name !== "AbortError") setError(e);
})
.finally(() => setLoading(false));
return () => controller.abort(); // 後片付け
}, [url]);
return { data, loading, error };
}
function UserList() {
const { data, loading, error } = useFetch("/api/users");
if (loading) return <p>読み込み中</p>;
if (error) return <p>取得に失敗しました</p>;
return <ul>{data.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}つまずきやすいところ
依存配列にオブジェクトを入れると、毎回新しい参照になって取得が止まらなくなります。useFetch(url, { headers }) のような形にするなら、オプションは useMemo で固定するか、依存には文字列化した値を渡します。
画面から離れた後に結果が届き、消えたコンポーネントへ状態を書き込む問題も定番です。AbortController で中断し、後片付けの関数で abort() を呼びます。
開発モードの StrictMode では useEffect が2回走ります。通信が二重に飛ぶのは仕様であり、本番では起きません。ここで慌てて StrictMode を外すと、後片付けの不備を見つける機会を失います。
似た用語との違い
自作の useFetch はキャッシュを持たないため、同じ URL を3つのコンポーネントが使えば3回通信します。画面をまたいだキャッシュ、再取得、重複排除まで欲しくなった時点が、SWR や TanStack Query へ移る目安です。