swr について
React と TypeScript で SWR を使ったデータ取得を行うためのガイダンススキルです。キャッシュ、再検証、変更、ページネーション、エラー処理を扱います。
主な機能
- 01古いデータを返して再取得
- 02キャッシュの自動再検証
- 03重複リクエストの統合
- 04変更と楽観的更新
- 05ページネーションと無限読み込み
活用シーン
- 01React でのクライアントデータ取得
- 02フォーカス時や再接続時の更新
- 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 つの場合にデータを自動的に再検証します。
- コンポーネントがマウントされたとき(キャッシュデータがある場合も含む)
- ウィンドウにフォーカスが当たったとき
- ブラウザーがネットワークに再接続したとき
// 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>
);
}
キーに関する規則
- 最小限の設定で、クライアント側のデータ取得に SWR を使用します
- ローディング状態やエラーメッセージに対して、条件付きレンダリングを実装します
- データを検証して想定される形式であることを確認し、エラーを適切に処理します
- ユーザーがデータを要求する前にプリフェッチを行い、パフォーマンスを向上させます
- 自動再検証を活用し、データをリアルタイムで最新の状態に保ちます
- ページネーションや無限スクロールのパターンには
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 型を無視しないでください
コメント
Markdown 対応