SWR の使い方 — React データフェッチングの定番ライブラリ
一言でいうと
SWR は、Vercel チームが開発した React Hooks ベースのデータフェッチングライブラリです。「stale-while-revalidate」戦略(キャッシュを先に返し、裏で再検証する)により、高速かつ常に最新のUIを実現します。
どんな時に使う?
- REST API からのデータ取得を簡潔に書きたい時 —
useEffect+useStateの定型コードを排除し、ローディング・エラー・データの状態管理を1つのフックに集約できます - リアルタイム性の高いダッシュボードやSNSフィードを作る時 — フォーカス復帰時の自動再検証、ポーリング、ネットワーク復帰時の再取得などが組み込みで提供されます
- 楽観的UI更新(Optimistic UI)を実装したい時 — ローカルミューテーションにより、サーバー応答を待たずにUIを即座に更新し、ユーザー体験を向上させられます
インストール
# npm
npm install swr
# yarn
yarn add swr
# pnpm
pnpm add swr
React 16.11.0 以上が必要です。
SWR の基本的な使い方
import useSWR from 'swr'
// fetcher は key(URL)を受け取り、データを返す非同期関数
const fetcher = (url: string): Promise<any> =>
fetch(url).then((res) => res.json())
type User = {
id: number
name: string
email: string
}
function Profile() {
const { data, error, isLoading } = useSWR<User>('/api/user', fetcher)
if (error) return <div>エラーが発生しました</div>
if (isLoading) return <div>読み込み中...</div>
return (
<div>
<h1>{data?.name}</h1>
<p>{data?.email}</p>
</div>
)
}
useSWR は key(リクエストの一意識別子、通常はURL)と fetcher(データ取得関数)を受け取ります。同じ key を持つ複数のコンポーネントがあっても、リクエストは自動的に重複排除されます。
よく使う API
1. useSWR — 基本のデータフェッチング
import useSWR, { SWRConfiguration } from 'swr'
type Repository = {
stargazers_count: number
forks_count: number
}
const options: SWRConfiguration = {
revalidateOnFocus: true, // フォーカス時に再検証(デフォルト: true)
revalidateOnReconnect: true, // ネットワーク復帰時に再検証
refreshInterval: 0, // ポーリング間隔(ms)。0で無効
dedupingInterval: 2000, // 重複排除の間隔(ms)
errorRetryCount: 3, // エラー時のリトライ回数
}
function RepoInfo() {
const { data, error, isLoading, isValidating, mutate } = useSWR<Repository>(
'https://api.github.com/repos/vercel/swr',
fetcher,
options
)
return (
<div>
{isValidating && <span>更新中...</span>}
<p>⭐ {data?.stargazers_count}</p>
<button onClick={() => mutate()}>手動で再取得</button>
</div>
)
}
返り値の主要プロパティ:
| プロパティ | 型 | 説明 |
|---|---|---|
data | T | undefined | フェッチされたデータ |
error | any | fetcher がスローしたエラー |
isLoading | boolean | 初回ロード中(キャッシュなし & リクエスト中) |
isValidating | boolean | リクエスト中(初回・再検証問わず) |
mutate | function | キャッシュを更新する関数 |
2. mutate — ローカルミューテーション(楽観的更新)
import useSWR from 'swr'
type Todo = {
id: number
title: string
completed: boolean
}
function TodoItem({ id }: { id: number }) {
const { data, mutate } = useSWR<Todo>(`/api/todos/${id}`, fetcher)
const toggleComplete = async () => {
const updated = { ...data!, completed: !data!.completed }
// 楽観的更新: サーバー応答を待たずにUIを即座に更新
await mutate(
async () => {
const res = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
body: JSON.stringify({ completed: updated.completed }),
headers: { 'Content-Type': 'application/json' },
})
return res.json()
},
{
optimisticData: updated, // 即座にUIに反映するデータ
rollbackOnError: true, // エラー時にキャッシュを元に戻す
revalidate: false, // ミューテーション後の再検証をスキップ
}
)
}
return (
<label>
<input
type="checkbox"
checked={data?.completed ?? false}
onChange={toggleComplete}
/>
{data?.title}
</label>
)
}
3. useSWRInfinite — ページネーション / 無限スクロール
import useSWRInfinite from 'swr/infinite'
type Issue = {
id: number
title: string
}
const PAGE_SIZE = 10
function Issues() {
const getKey = (pageIndex: number, previousPageData: Issue[] | null) => {
// 最後のページに到達した場合は null を返してフェッチを停止
if (previousPageData && previousPageData.length === 0) return null
return `/api/issues?page=${pageIndex + 1}&limit=${PAGE_SIZE}`
}
const { data, size, setSize, isLoading, isValidating } =
useSWRInfinite<Issue[]>(getKey, fetcher)
// 全ページのデータをフラットに結合
const issues = data ? data.flat() : []
const isReachingEnd = data && data[data.length - 1]?.length < PAGE_SIZE
return (
<div>
{issues.map((issue) => (
<div key={issue.id}>{issue.title}</div>
))}
{!isReachingEnd && (
<button
onClick={() => setSize(size + 1)}
disabled={isValidating}
>
{isValidating ? '読み込み中...' : 'もっと読み込む'}
</button>
)}
</div>
)
}
4. SWRConfig — グローバル設定
import { SWRConfig } from 'swr'
const globalFetcher = (url: string) =>
fetch(url).then((res) => {
if (!res.ok) throw new Error('API Error')
return res.json()
})
function App() {
return (
<SWRConfig
value={{
fetcher: globalFetcher, // 全フックで共通の fetcher
refreshInterval: 30000, // 30秒ごとにポーリング
revalidateOnFocus: true,
shouldRetryOnError: true,
errorRetryInterval: 5000,
onError: (error, key) => {
console.error(`SWR Error [${key}]:`, error)
},
}}
>
<Dashboard />
</SWRConfig>
)
}
// SWRConfig 配下では fetcher を省略可能
function Dashboard() {
const { data } = useSWR<{ revenue: number }>('/api/stats')
return <div>売上: {data?.revenue}</div>
}
5. useSWRMutation — 手動トリガーのミューテーション
import useSWRMutation from 'swr/mutation'
type CreateUserPayload = {
name: string
email: string
}
type User = {
id: number
name: string
email: string
}
// 第2引数の arg にトリガー時に渡したデータが入る
async function createUser(url: string, { arg }: { arg: CreateUserPayload }): Promise<User> {
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify(arg),
headers: { 'Content-Type': 'application/json' },
})
return res.json()
}
function CreateUserForm() {
const { trigger, isMutating, error } = useSWRMutation<User, Error, string, CreateUserPayload>(
'/api/users',
createUser
)
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
try {
const newUser = await trigger({
name: formData.get('name') as string,
email: formData.get('email') as string,
})
console.log('作成されたユーザー:', newUser)
} catch (err) {
// エラーハンドリング
}
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="名前" required />
<input name="email" type="email" placeholder="メール" required />
<button type="submit" disabled={isMutating}>
{isMutating ? '送信中...' : 'ユーザー作成'}
</button>
{error && <p>エラー: {error.message}</p>}
</form>
)
}
類似パッケージとの比較
| 特徴 | SWR | TanStack Query (React Query) | Apollo Client |
|---|---|---|---|
| バンドルサイズ (minzip) | ~4.5 KB | ~13 KB | ~33 KB |
| プロトコル | 任意(REST, GraphQL 等) | 任意(REST, GraphQL 等) | GraphQL 特化 |
| キャッシュ戦略 | stale-while-revalidate | stale-while-revalidate | 正規化キャッシュ |
| DevTools | なし(公式) | 充実 | 充実 |
| ページネーション | useSWRInfinite | useInfiniteQuery | fetchMore |
| 楽観的更新 | ○ | ○ | ○ |
| SSR/SSG サポート | ○ | ○ | ○ |
| ミューテーション管理 | useSWRMutation | useMutation(より高機能) | useMutation |
| 学習コスト | 低い | やや高い | 高い |
| 依存関係 | React のみ | React のみ | GraphQL エコシステム |
選定の目安:
- SWR — シンプルさ・軽量さを重視。Next.js プロジェクトとの親和性が高い
- TanStack Query — 複雑なキャッシュ管理、DevTools、ミューテーション管理が必要な場合
- Apollo Client — GraphQL API を使う場合
注意点・Tips
条件付きフェッチ
key に null や falsy な値を渡すとフェッチをスキップできます。認証状態に応じたデータ取得などに便利です。
// userId が存在しない場合はフェッチしない
const { data } = useSWR<User>(userId ? `/api/users/${userId}` : null, fetcher)
fetcher でのエラーハンドリング
fetch API はネットワークエラー以外(4xx, 5xx)では例外をスローしません。fetcher 内で明示的にエラーを投げる必要があります。
const fetcher = async (url: string) => {
const res = await fetch(url)
if (!res.ok) {
const error = new Error('API request failed')
throw error
}
return res.json()
}
key のシリアライゼーション
配列やオブジェクトを key に使えます。SWR は内部で安定的にシリアライズします。
// クエリパラメータを含む key
const { data } = useSWR(['/api/users', { page: 1, limit: 10 }], ([url, params]) => {
const query = new URLSearchParams(params as Record<string, string>).toString()
return fetcher(`${url}?${query}`)
})
不要な再レンダリングの抑制
返り値の中で使わないフィールドがある場合、compare オプションや返り値の選択で再レンダリングを最適化できます。
// isValidating の変化では再レンダリングしない
const { data } = useSWR<User>('/api/user', fetcher, {
revalidateOnFocus: false,
})
React Suspense との統合
import { Suspense } from 'react'
import useSWR from 'swr'
function Profile() {
// suspense: true でデータが準備できるまで Suspense boundary に委譲
const { data } = useSWR<User>('/api/user', fetcher, { suspense: true })
// data は必ず存在する(undefined にならない)
return <div>{data.name}</div>
}
function App() {
return (
<Suspense fallback={<div>読み込み中...</div>}>
<Profile />
</Suspense>
)
}
プリフェッチ
ユーザーの操作を先読みして