mcpField
S
swr

swr

github.com/mindrally開発287879 回インストール3 週間前に更新
リポジトリ

swr について

React と TypeScript で SWR を使ったデータ取得を行うためのガイダンススキルです。キャッシュ、再検証、変更、ページネーション、エラー処理を扱います。

主な機能

  1. 01古いデータを返して再取得
  2. 02キャッシュの自動再検証
  3. 03重複リクエストの統合
  4. 04変更と楽観的更新
  5. 05ページネーションと無限読み込み

活用シーン

  1. 01React でのクライアントデータ取得
  2. 02フォーカス時や再接続時の更新
  3. 03ページネーションや無限リストの構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mindrally/skills --skill swr

インストール後、Claude Code で/swrを入力して読み込まれたか確認します。

詳細

SWR のベストプラクティス

あなたは、SWR(stale-while-revalidate)、TypeScript、React の開発に精通しています。SWR はデータ取得用の React Hooks ライブラリです。最初にキャッシュからデータ(stale)を返し、次にリクエストを送信して(revalidate)、最後に最新のデータを返します。

基本原則

  • クライアント側のデータ取得にはすべて SWR を使用します
  • 自動キャッシュと再検証を活用します
  • SWR に組み込まれた状態管理を利用して定型コードを最小限に抑えます
  • 適切なエラー処理とローディング状態を実装します
  • 完全な型安全性を実現するために TypeScript を使用します

主な機能

  • Stale-While-Revalidate:キャッシュデータをすぐに返し、その後バックグラウンドで再検証します
  • 自動再検証:マウント時、ウィンドウにフォーカスが当たったとき、ネットワーク再接続時に行います
  • リクエストの重複排除:同じキーを使用する複数のコンポーネントで、1 つのリクエストを共有します
  • 組み込みキャッシュ:スマートな無効化機能を備えた、設定不要のキャッシュです
  • 最小限の API:シンプルなフックベースのインターフェースです

プロジェクト構成

src/
  hooks/
    swr/
      useUser.ts
      usePosts.ts
      useProducts.ts
  lib/
    fetcher.ts           # Global fetcher configuration
  providers/
    SWRProvider.tsx      # SWR configuration provider
  types/
    api.ts               # API response types

セットアップと設定

グローバル設定
// providers/SWRProvider.tsx
import { SWRConfig } from 'swr';

const fetcher = async (url: string) => {
  const res = await fetch(url);

  if (!res.ok) {
    const error = new Error('An error occurred while fetching data.');
    throw error;
  }

  return res.json();
};

export function SWRProvider({ children }: { children: React.ReactNode }) {
  return (
    <SWRConfig
      value={{
        fetcher,
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        shouldRetryOnError: true,
        errorRetryCount: 3,
        dedupingInterval: 2000,
      }}
    >
      {children}
    </SWRConfig>
  );
}
カスタムフェッチャー
// lib/fetcher.ts
export async function fetcher<T>(url: string): Promise<T> {
  const response = await fetch(url, {
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return response.json();
}

// With authentication
export async function authFetcher<T>(url: string): Promise<T> {
  const token = getAuthToken();

  const response = await fetch(url, {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return response.json();
}

基本的な使い方

useSWR フック

useSWR フックは、次の 3 つのパラメーターを受け取ります。

  • key:リクエストを識別する一意の文字列(URL など)
  • fetcher:データを取得する非同期関数
  • options:設定オプション
import useSWR from 'swr';

interface User {
  id: string;
  name: string;
  email: string;
}

function useUser(userId: string) {
  const { data, error, isLoading, isValidating, mutate } = useSWR<User>(
    userId ? `/api/users/${userId}` : null,
    fetcher
  );

  return {
    user: data,
    isLoading,
    isError: !!error,
    isValidating,
    mutate,
  };
}
条件付きデータ取得

キーとして null または falsy な値を渡すと、条件に応じてデータ取得をスキップできます。

// Only fetch when userId is available
const { data } = useSWR(userId ? `/api/users/${userId}` : null, fetcher);

// Using a function for dynamic keys
const { data } = useSWR(() => `/api/users/${userId}`, fetcher);

再検証の方法

自動再検証

SWR は次の 3 つの場合にデータを自動的に再検証します。

  1. コンポーネントがマウントされたとき(キャッシュデータがある場合も含む)
  2. ウィンドウにフォーカスが当たったとき
  3. ブラウザーがネットワークに再接続したとき
// Disable specific revalidation behaviors
const { data } = useSWR('/api/data', fetcher, {
  revalidateOnFocus: false,
  revalidateOnReconnect: false,
  revalidateOnMount: true,
});
手動再検証
import { useSWRConfig } from 'swr';

function UpdateButton() {
  const { mutate } = useSWRConfig();

  const handleUpdate = async () => {
    // Revalidate specific key
    await mutate('/api/users');

    // Revalidate all keys matching a filter
    await mutate(
      (key) => typeof key === 'string' && key.startsWith('/api/users'),
      undefined,
      { revalidate: true }
    );
  };

  return <button onClick={handleUpdate}>Refresh</button>;
}
ポーリング/一定間隔での更新
// Refresh every 3 seconds
const { data } = useSWR('/api/realtime', fetcher, {
  refreshInterval: 3000,
});

// Conditional polling
const { data } = useSWR('/api/realtime', fetcher, {
  refreshInterval: isVisible ? 1000 : 0,
});

ミューテーションのパターン

楽観的更新
import useSWR, { useSWRConfig } from 'swr';

function useUpdateUser() {
  const { mutate } = useSWRConfig();

  const updateUser = async (userId: string, newData: Partial<User>) => {
    // Optimistic update
    await mutate(
      `/api/users/${userId}`,
      async (currentData: User | undefined) => {
        // Update API
        const updated = await fetch(`/api/users/${userId}`, {
          method: 'PATCH',
          body: JSON.stringify(newData),
        }).then(r => r.json());

        return updated;
      },
      {
        optimisticData: (current) => ({ ...current, ...newData } as User),
        rollbackOnError: true,
        populateCache: true,
        revalidate: false,
      }
    );
  };

  return { updateUser };
}
バインドされた mutate
function UserProfile({ userId }: { userId: string }) {
  const { data: user, mutate } = useSWR(`/api/users/${userId}`, fetcher);

  const handleUpdate = async (newName: string) => {
    // Update local data immediately
    mutate({ ...user, name: newName }, false);

    // Send update to server
    await updateUserName(userId, newName);

    // Revalidate to ensure consistency
    mutate();
  };

  return (
    <div>
      <p>{user?.name}</p>
      <button onClick={() => handleUpdate('New Name')}>Update</button>
    </div>
  );
}

ページネーション

基本的なページネーション
function usePaginatedUsers(page: number) {
  return useSWR(`/api/users?page=${page}`, fetcher, {
    keepPreviousData: true,
  });
}
useSWRInfinite による無限ローディング
import useSWRInfinite from 'swr/infinite';

function useInfiniteUsers() {
  const getKey = (pageIndex: number, previousPageData: User[] | null) => {
    // Return null to stop fetching
    if (previousPageData && previousPageData.length === 0) return null;

    // Return key for next page
    return `/api/users?page=${pageIndex + 1}`;
  };

  const { data, error, size, setSize, isValidating } = useSWRInfinite(
    getKey,
    fetcher
  );

  const users = data ? data.flat() : [];
  const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined';
  const isEmpty = data?.[0]?.length === 0;
  const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 10);

  return {
    users,
    isLoadingMore,
    isReachingEnd,
    loadMore: () => setSize(size + 1),
  };
}
カーソルベースのページネーション
import useSWRInfinite from 'swr/infinite';

function useCursorPagination() {
  const getKey = (pageIndex: number, previousPageData: any) => {
    if (previousPageData && !previousPageData.nextCursor) return null;

    if (pageIndex === 0) return '/api/items';

    return `/api/items?cursor=${previousPageData.nextCursor}`;
  };

  return useSWRInfinite(getKey, fetcher);
}

エラー処理

コンポーネントレベルのエラー処理
function UserProfile({ userId }: { userId: string }) {
  const { data, error, isLoading } = useSWR(`/api/users/${userId}`, fetcher);

  if (isLoading) return <Skeleton />;

  if (error) {
    return (
      <ErrorMessage
        message="Failed to load user profile"
        retry={() => mutate(`/api/users/${userId}`)}
      />
    );
  }

  return <ProfileCard user={data} />;
}
グローバルエラーハンドラー
<SWRConfig
  value={{
    onError: (error, key) => {
      if (error.status !== 403 && error.status !== 404) {
        // Send error to tracking service
        reportError(error, key);
      }
    },
  }}
>
  <App />
</SWRConfig>

プリフェッチ

import { preload } from 'swr';

// Prefetch data before component renders
function prefetchUser(userId: string) {
  preload(`/api/users/${userId}`, fetcher);
}

// Usage: Call on hover or route change
function UserLink({ userId }: { userId: string }) {
  return (
    <Link
      to={`/users/${userId}`}
      onMouseEnter={() => prefetchUser(userId)}
    >
      View Profile
    </Link>
  );
}

キーに関する規則

  1. 最小限の設定で、クライアント側のデータ取得に SWR を使用します
  2. ローディング状態やエラーメッセージに対して、条件付きレンダリングを実装します
  3. データを検証して想定される形式であることを確認し、エラーを適切に処理します
  4. ユーザーがデータを要求する前にプリフェッチを行い、パフォーマンスを向上させます
  5. 自動再検証を活用し、データをリアルタイムで最新の状態に保ちます
  6. ページネーションや無限スクロールのパターンには useSWRInfinite を使用します

Next.js との連携

// For server-side data, use getServerSideProps or getStaticProps
// For client-side caching and revalidation, use SWR

// pages/user/[id].tsx
export async function getStaticProps({ params }) {
  const user = await fetchUser(params.id);
  return { props: { fallback: { [`/api/users/${params.id}`]: user } } };
}

export default function UserPage({ fallback }) {
  return (
    <SWRConfig value={{ fallback }}>
      <UserProfile />
    </SWRConfig>
  );
}

避けるべきアンチパターン

  • SWR で処理できるデータ取得に useEffect を使用しないでください
  • エラー状態を無視せず、必ずユーザーにフィードバックを提供してください
  • ローディング状態を適切に処理することを忘れないでください
  • WebSocket や SSE のほうが効率的な場合は、ポーリングを使用しないでください
  • 依存するデータの条件付きデータ取得を省略しないでください
  • 取得したデータの TypeScript 型を無視しないでください

コメント