Kodokon kodokon.com

API を呼び出す: fetch、ローディング、エラー

副作用の中で fetch を使ってリモートデータを読み込み、ローディングとエラーの状態をきれいにモデリングします。

10 分 · 3 問

このレッスンを Kodokon で開く

ネットワークリクエストには時間がかかり、失敗する可能性もあります。インターフェイスはそうした現実を反映しなければなりません。プロの慣習は、3 つの異なる state を追跡することです。受け取ったデータローディングを表す真偽値、そして起こりうるエラーです。サーバーに接続するのは副作用なので、リクエストは useEffect の中に入れます。

JSX
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  fetch("https://api.example.com/users")
    .then((res) => {
      if (!res.ok) throw new Error("HTTP " + res.status);
      return res.json();
    })
    .then((data) => setUsers(data))
    .catch((err) => setError(err.message))
    .finally(() => setLoading(false));
}, []);
1 つのリクエストに 3 つの状態: データ、ローディング、エラー。

レンダリングでは、ケースを順番に処理します。まずローディング、次にエラー、最後にデータです。これらの早期リターンはコードを読みやすく保ち、ユーザーが常に一貫した何か、つまり待機中のインジケーター、失敗のメッセージ、または期待されるリストを見ることを保証します。

JSX
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error}</p>;

return (
  <ul>
    {users.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);
早期リターン: 一度に表示されるケースは 1 つだけ。

最後の落とし穴です。依存関係(例えば userId)が変わると、古いリクエストがまだ処理中かもしれないうちに新しいリクエストが送られます。もし古い方が最後に応答すると、新しいデータを上書きしてしまいます。定番の対策は、クリーンアップ関数で true に設定する ignore フラグで、古いレスポンスを捨てることです。

JSX
useEffect(() => {
  let ignore = false;
  setLoading(true);

  fetch("/api/users/" + userId)
    .then((res) => res.json())
    .then((data) => {
      if (!ignore) setUser(data);
    })
    .finally(() => {
      if (!ignore) setLoading(false);
    });

  return () => {
    ignore = true;
  };
}, [userId]);
フラグのおかげで、古いレスポンスはすべて破棄されます。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. なぜ 1 つのリクエストに対して 3 つの状態(データ、ローディング、エラー)を追跡するのですか?
    • fetch が動作するにはこの 3 つの変数が必要だから
    • インターフェイスが各段階(待機、失敗、成功)を反映するように
    • React がレンダリングのたびにリクエストを再発火するのを防ぐため
  2. API が 500 ステータスで応答したのに、catch が発動しません。なぜですか?
    • fetch はネットワークの失敗でのみ Promise を reject し、HTTP エラーステータスでは reject しないから
    • catch は useEffect の中では動作しないから
    • catch を発動させるにはサーバーが特別なヘッダーを送らなければならないから
  3. 副作用のクリーンアップで true に設定される ignore フラグは、何のために使われますか?
    • 処理中の HTTP リクエストをサーバー側でキャンセルするため
    • React が副作用を再実行するのを防ぐため
    • 古くなったリクエストのレスポンスを破棄するため