本文へスキップ
BecomeCoder

Reactコース · 第6章 実アプリとエコシステム · レッスン18

データ取得のパターン ― ローディングとエラー表示

ブラウザで完結

導入

実際のアプリでは、画面を開いた瞬間にはデータがなく、サーバーに問い合わせて届くまで少し時間がかかります。この「まだ読み込み中」「取得に失敗した」「取得できた」という3つの状態を、そのまま state として持つのが定番のパターンです。この学習環境では本物のネットワーク通信ができないので、setTimeout で「時間がかかる疑似API」を再現しますが、パターンそのものは実際の fetch を使うときとまったく同じです。

説明

sequenceDiagram
  participant U as ユーザー操作
  participant C as コンポーネント
  participant A as 疑似API
  U->>C: idを変更
  C->>C: status を "loading" に
  C->>A: fakeFetchUser(id)
  A-->>C: 成功 または 失敗
  C->>C: status を "success" / "error" に
// 本物の fetch の代わりに、時間がかかる疑似APIを用意する
function fakeFetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id <= 0) {
        reject(new Error("不正なIDです"));
      } else {
        resolve({ id, name: `ユーザー${id}` });
      }
    }, 800);
  });
}

function App() {
  const [id, setId] = React.useState(1);
  const [user, setUser] = React.useState(null);
  const [status, setStatus] = React.useState("loading"); // "loading" | "success" | "error"

  React.useEffect(() => {
    let ignore = false; // 古い応答を無視するためのフラグ
    setStatus("loading");
    fakeFetchUser(id)
      .then((data) => {
        if (!ignore) {
          setUser(data);
          setStatus("success");
        }
      })
      .catch(() => {
        if (!ignore) setStatus("error");
      });
    return () => {
      ignore = true;
    };
  }, [id]);

  return (
    <div style={{ fontFamily: "system-ui", maxWidth: 320 }}>
      <button onClick={() => setId((n) => n + 1)}>次のユーザー</button>{" "}
      <button onClick={() => setId(-1)}>不正なIDを試す</button>
      {status === "loading" && <p>読み込み中...</p>}
      {status === "error" && <p style={{ color: "crimson" }}>読み込みに失敗しました</p>}
      {status === "success" && <p>{user.name}(id: {user.id})</p>}
    </div>
  );
}
  • status"loading" / "success" / "error" の3値で持ち、JSX 側は {status === "..." && ...} で出し分けます。これが「取得中の画面が真っ白/固まったように見える」問題を防ぐ基本形です。
  • useEffect の依存配列[id] を入れているので、id が変わるたびに取得し直します。
  • let ignore = false とクリーンアップでの ignore = trueid を素早く連打すると、古いリクエストの応答が新しい応答より後に届くことがあります。これを無視しないと、画面が古いデータで上書きされてしまいます(競合状態、race condition と呼ばれるバグです)。
  • 実務では fakeFetchUser(id) の部分が fetch("/api/users/" + id).then(r => r.json()) に変わるだけで、考え方は同じです。

やってみよう

「次のユーザー」を連打して、途中の読み込みが飛ばされて最新の結果だけが表示されることを確認しましょう。「不正なIDを試す」を押してエラー表示になることも確かめてください。

実際に動かしてみよう

下のエディタにReactのコードを書くと、右側のプレビューにその場で描画されます。`function App() { ... }` を定義すると自動で表示されます(React・useState などは React.useState の形で使えます)。本文の例を打ち込んだり書き換えたりして、表示の変化を確かめながら進めましょう。

React — ブラウザ内で実行

JSXをその場で変換して描画する学習用React環境(React18 + Babel・同梱)を読み込みます。
スクロールして表示された時点でも自動で読み込まれます。