Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

useSWR入門

💡 NotebookLM で解説を聞く

データ取得をもっと楽に、もっと速く。そんな願いを叶えるのが「SWR」です。 ReactのuseEffect + fetchよりも簡単なコードで、またキャッシュ管理・バックグラウンド更新・エラーハンドリング・再試行などを自動で行ってくれます。

この記事で学べること

  • SWRの基本概念(Stale-While-Revalidate)
  • データ取得
  • 状態管理(isLoading/error/data)
  • グローバル設定(SWRConfig)
  • 再検証のタイミング制御(focus/reconnect/interval)
  • 依存キー・条件付きフェッチ
  • ミューテーション(書き込みとキャッシュ更新)

基本概念: Stale-While-Revalidate とは

SWRは「手元のキャッシュ(stale)をすぐ表示しつつ、裏で最新データを取りに行き(revalidate)、更新できたらUIを差し替える」という戦略です。 ユーザーは待たされず、でもデータは最新、という理想的なユーザー体験が得られます。いいとこ取りというわけです。

useSWRを使ってみる

インストール(プロジェクトで一度だけ)

pnpm i swr

基本の使い方:

import useSWR from "swr";

const API_BASE = "https://jsonplaceholder.typicode.com";

async function fetcher(url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP Error: ${res.status}`);
  return res.json();
}

function UserDetail({ userId }) {
  const { data, error, isLoading } = useSWR(
    `${API_BASE}/users/${userId}`,
    fetcher,
  );

  if (isLoading) return <p>読み込み中...</p>;
  if (error) return <p>エラーが発生しました</p>;

  return (
    <div>
      <h4>{data.name}</h4>
      <p>{data.email}</p>
      <small>ID: {data.id}</small>
    </div>
  );
}

Note: fetcherは「URLを受け取ってデータを返す関数」。SWRはこのfetcherにURL(キー)を渡して実行します。エラー時は例外を投げることで、SWRのerror状態が有効になります。

状態管理

SWRは非同期のデータ取得に必要な状態を管理する機能を内蔵しています。返り値のオブジェクトから以下の3つを取り出して使います。

  • data: 取得済みデータ(キャッシュを含む)
  • isLoading: 読み込み中
  • error: フェッチに失敗したときのエラー

Note: すでにキャッシュがあればisLoadingでもdataがある、という状態も起こり得ます(これがSWRのポイント)。

SWRConfig でグローバル設定

import { SWRConfig } from "swr";

async function fetcher(url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP Error: ${res.status}`);
  return res.json();
}

function Providers({ children }) {
  return <SWRConfig value={{ fetcher }}>{children}</SWRConfig>;
}

// 使い方
function App() {
  return (
    <Providers>
      <UserDetail userId={1} />
    </Providers>
  );
}

Note: ここで指定したfetcherがデフォルトになります。各コンポーネントでfetcherを省略可能に(楽ですね)。

再検証タイミングをコントロール

  • revalidateOnFocus: タブに戻ったら再取得(ユーザーに最新を見せる)
  • revalidateOnReconnect: ネットワーク復帰で再取得
  • refreshInterval: 定期ポーリング(ms)。0で無効
function UserDetail({ userId, enableRefresh = false }) {
  const { data, error, isLoading } = useSWR(`${API_BASE}/users/${userId}`, {
    refreshInterval: enableRefresh ? 5000 : 0, // 5秒ごとに再フェッチ
  });

  // ...
}

Note: refreshIntervalを使うと、一定間隔でデータが自動更新されます。リアルタイム性が必要な場面で便利です。

依存キーと条件付きフェッチ

キー(第1引数)にnullを渡すとフェッチしません。必要な条件がそろうまで待てます。

function Profile() {
  const [userId, setUserId] = useState("");
  const [submittedUserId, setSubmittedUserId] = useState("");

  // submittedUserIdが空の場合はフェッチしない
  const { data, error, isLoading } = useSWR(
    submittedUserId ? `${API_BASE}/users/${submittedUserId}` : null,
  );

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        setSubmittedUserId(userId.trim());
      }}
    >
      <input
        type="text"
        value={userId}
        onChange={(e) => setUserId(e.target.value)}
        placeholder="ユーザーID"
      />
      <button type="submit">検索</button>
    </form>
  );
}

Note: フォーム入力が完了して「検索」ボタンを押すまでリクエストは発生しません。

ミューテーションでUIを即時更新

mutateはキャッシュを書き換え、UIを即時反映させる関数です。サーバー反映を待たずに「先に見た目を更新」できます(便利)。

function Profile() {
  const [submittedUserId, setSubmittedUserId] = useState("");

  const { data, error, mutate } = useSWR(
    submittedUserId ? `${API_BASE}/users/${submittedUserId}` : null,
  );

  // 存在しないユーザーをローカルで「作成」する(mutateでキャッシュに直接書き込む)
  const createDummyUser = () => {
    mutate(
      {
        id: Number(submittedUserId),
        name: `ダミーユーザー ${submittedUserId}`,
        email: `dummy${submittedUserId}@example.com`,
        phone: "000-0000-0000",
      },
      { revalidate: false }, // サーバーに再リクエストしない
    );
  };

  // エラー時に再試行
  const retry = () => mutate();

  // ...
}

Note: mutate()を引数なしで呼ぶと再フェッチ、データを渡すとキャッシュを直接書き換えます。{ revalidate: false }でサーバーへの再リクエストを抑制できます。

SWRでのエラーハンドリング戦略

エラー発生時の回復パターンを見てみましょう。

function Profile() {
  const [submittedUserId, setSubmittedUserId] = useState("");
  const [shouldRetryOnError, setShouldRetryOnError] = useState(false);

  const { data, error, isLoading, mutate } = useSWR(
    submittedUserId ? `${API_BASE}/users/${submittedUserId}` : null,
    { shouldRetryOnError }, // エラー時の自動リトライを制御
  );

  return (
    <>
      {error && !isLoading && (
        <div>
          <p>ユーザー {submittedUserId} が見つかりません</p>
          <button onClick={() => mutate()}>再試行</button>
          <button onClick={createDummyUser}>ユーザーを作成</button>
        </div>
      )}

      {data && !error && (
        <div>
          <p>{data.name}</p>
          <p>{data.email}</p>
        </div>
      )}
    </>
  );
}

Note: shouldRetryOnErrorオプションでエラー時の自動リトライを制御できます。手動で再試行させたい場合はfalseに設定し、mutate()で明示的に再フェッチします。

やってみよう!

Open in StackBlitz

サンプルコードには3つのセクションがあります。

1. キャッシュ共有 + 定期更新

  • 同じURLを参照する複数のコンポーネントがキャッシュを共有することを確認
  • ユーザーIDを切り替えて、2回目以降は即座に表示される(キャッシュヒット)ことを体験
  • DevTools Networkで1回のみリクエストされることを確認
  • 「自動更新」チェックボックスで5秒間隔のrefreshIntervalを体験

2. キーの先行切り替え

  • IDを変更すると、IDは即座に切り替わるがデータは後から到着する様子を観察
  • SWRのキー変更でリクエストが発生する仕組みを理解

3. 条件付きフェッチ + エラーハンドリング

  • IDを入力して「検索」を押すまでリクエストが発生しないことを確認
  • 存在しないID(11以上)を入力してエラーハンドリングを体験
  • 「再試行」ボタンでmutate()による再フェッチを体験
  • 「ユーザーを作成」ボタンでmutate()によるキャッシュ書き込みを体験

Tip: Chrome DevTools > Network を「低速 4G」にすると、SWRの挙動がより分かりやすくなります。

ポイント(まとめ)

  • SWRは「キャッシュ優先+裏で再取得」の戦略
  • useSWR(key, fetcher) が基本形
  • 状態(loading/error/data)を内蔵していてUIが簡単
  • SWRConfigで全体方針を一括設定
  • フォーカス/再接続/ポーリングで最新化タイミングを制御
  • mutateでキャッシュを書き換えてUIを即反映
  • nullキーで条件付きフェッチ、配列キーで柔軟に渡す

参考リンク