SWR — React Hooks for Data Fetching
You are an expert in SWR v2 (latest: 2.4.1), the React Hooks library for data fetching by Vercel. SWR implements the stale-while-revalidate HTTP cache invalidation strategy — serve from cache first, then revalidate in the background.
Installation
npm install swr
Core API
useSWR
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then(res => res.json())
function Profile() {
const { data, error, isLoading, mutate } = useSWR('/api/user', fetcher)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error loading data</div>
return <div>Hello, {data.name}</div>
}
Key parameters:
key— unique string, array, or function identifying the resource (often a URL)fetcher— async function that receives the key and returns dataoptions— optional config object
Return values: data, error, isLoading, isValidating, mutate
useSWRMutation — Remote Mutations
import useSWRMutation from 'swr/mutation'
async function updateUser(url: string, { arg }: { arg: { name: string } }) {
return fetch(url, { method: 'POST', body: JSON.stringify(arg) }).then(res => res.json())
}
function Profile() {
const { trigger, isMutating } = useSWRMutation('/api/user', updateUser)
return (
<button disabled={isMutating} onClick={() => trigger({ name: 'New Name' })}>
Update
</button>
)
}
useSWRInfinite — Pagination & Infinite Loading
import useSWRInfinite from 'swr/infinite'
const getKey = (pageIndex: number, previousPageData: any[]) => {
if (previousPageData && !previousPageData.length) return null
return `/api/items?page=${pageIndex}`
}
function Items() {
const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher)
const items = data ? data.flat() : []
return (
<>
{items.map(item => <div key={item.id}>{item.name}</div>)}
<button onClick={() => setSize(size + 1)}>Load More</button>
</>
)
}
Global Configuration
Wrap your app (or a subtree) with SWRConfig to set defaults:
import { SWRConfig } from 'swr'
function App() {
return (
<SWRConfig value={{
fetcher: (url: string) => fetch(url).then(res => res.json()),
revalidateOnFocus: false,
dedupingInterval: 5000,
}}>
<Dashboard />
</SWRConfig>
)
}
Revalidation Strategies
| Strategy | Option | Default |
|---|---|---|
| On window focus | revalidateOnFocus | true |
| On network recovery | revalidateOnReconnect | true |
| On mount if stale | revalidateIfStale | true |
| Polling | refreshInterval | 0 (disabled) |
| Manual | Call mutate() | — |
Optimistic Updates
const { trigger } = useSWRMutation('/api/user', updateUser, {
optimisticData: (current) => ({ ...current, name: 'New Name' }),
rollbackOnError: true,
populateCache: true,
revalidate: false,
})
Conditional Fetching
Pass null or a falsy key to skip fetching:
const { data } = useSWR(userId ? `/api/user/${userId}` : null, fetcher)
Error Retry
SWR retries on error by default with exponential backoff. Customize with:
useSWR(key, fetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return // Don't retry on 404
if (retryCount >= 3) return // Max 3 retries
setTimeout(() => revalidate({ retryCount }), 5000)
},
})
useSWRSubscription — Real-Time Data Sources
Subscribe to real-time data (WebSockets, SSE, etc.) with automatic deduplication:
import useSWRSubscription from 'swr/subscription'
function LivePrice({ symbol }: { symbol: string }) {
const { data } = useSWRSubscription(
`wss://stream.example.com/${symbol}`,
(key, { next }) => {
const ws = new WebSocket(key)
ws.onmessage = (event) => next(null, JSON.parse(event.data))
ws.onerror = (event) => next(event)
return () => ws.close()
}
)
return <span>{data?.price}</span>
}
The subscribe function receives a next(error, data) callback and must return a cleanup function. Multiple components using the same key share a single subscription.
Key Rules
- Keys must be unique — two
useSWRcalls with the same key share cache and deduplicate requests - Fetcher is optional when set via
SWRConfig mutate(key)globally revalidates any hook matching that key- Array keys like
useSWR(['/api/user', id], fetcher)— the fetcher receives the full array - Never call hooks conditionally — use conditional keys (
null) instead