SWR Core Knowledge
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: swr for comprehensive documentation.
Setup
npm install swr
When NOT to Use This Skill
| Scenario |
Use Instead |
| Complex cache invalidation needs |
tanstack-query for advanced features |
| Vue 3 applications |
Composables with useFetch or useAsyncData |
| Client-side UI state |
zustand or React state |
| GraphQL queries |
@apollo/client or urql |
| Need offline support |
tanstack-query with persist plugin |
Anti-Patterns
| Anti-Pattern |
Why It's Bad |
Correct Approach |
| Using SWR for client state |
Wrong abstraction, unnecessary complexity |
Use Zustand or React state |
| Not providing global fetcher |
Repetitive code, inconsistent error handling |
Configure global fetcher in <SWRConfig> |
| Mutating without revalidation |
Stale data shown to users |
Call mutate() after updates |
| Fetching on every render |
Performance issues, too many requests |
Use stable key, leverage cache |
| Not handling error states |
Poor UX, broken UI |
Always check error property |
| Using different keys for same data |
Cache fragmentation, duplication |
Standardize key format across app |
| No loading states |
Bad perceived performance |
Use isLoading or isValidating |
| Manual cache updates everywhere |
Hard to maintain |
Use mutate() with optimistic updates |
Ignoring isValidating |
Confusing UX during background refetch |
Show subtle indicator when revalidating |
| Not using conditional fetching |
Unnecessary requests |
Pass null key when data not needed |
Quick Troubleshooting
| Issue |
Cause |
Solution |
| Data not updating |
revalidateOnFocus disabled |
Enable in SWRConfig or use mutate() |
| "Cannot read property of undefined" |
No loading check before accessing data |
Add if (isLoading) return <Loading /> |
| Multiple requests for same key |
No deduplication interval |
Set dedupingInterval in config |
| Stale data after mutation |
Not calling mutate() |
Call mutate(key) or mutate() from hook |
| Memory leaks |
SWR instance not cleaned up |
Ensure proper component unmounting |
| TypeScript errors |
No generic type |
Use useSWR<DataType>(key, fetcher) |
| Infinite refetch loop |
Key changes on every render |
Use useMemo or stable key |
| 401 errors not handled |
No global error handler |
Configure error handler in <SWRConfig> |
Core Patterns
Basic Fetching
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(res => res.json());
function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useSWR(`/api/users/${userId}`, fetcher);
if (error) return <div>Failed to load</div>;
if (isLoading) return <div>Loading...</div>;
return <div>Hello {data.name}!</div>;
}
Global Configuration
import { SWRConfig } from 'swr';
function App() {
return (
<SWRConfig
value={{
fetcher: (url) => fetch(url).then(res => res.json()),
revalidateOnFocus: true,
dedupingInterval: 2000,
}}
>
<MyApp />
</SWRConfig>
);
}
Mutation
import useSWR, { mutate } from 'swr';
function UpdateUser() {
const { data } = useSWR('/api/user', fetcher);
async function handleUpdate() {
await fetch('/api/user', {
method: 'PUT',
body: JSON.stringify({ name: 'New Name' })
});
// Revalidate the cache
mutate('/api/user');
}
return <button
}
Optimistic Updates
const { data, mutate } = useSWR('/api/user', fetcher);
async function updateUser(newData: User) {
// Optimistic update
mutate({ ...data, ...newData }, false);
// Send request
await fetch('/api/user', {
method: 'PUT',
body: JSON.stringify(newData)
});
// Revalidate
mutate();
}
Conditional Fetching
// Only fetch if userId exists
const { data } = useSWR(userId ? `/api/users/${userId}` : null, fetcher);
Pagination
function UserList() {
const [page, setPage] = useState(1);
const { data, isLoading } = useSWR(`/api/users?page=${page}`, fetcher);
return (
<>
{data?.users.map(user => <UserCard key={user.id} user={user} />)}
<button => setPage(p => p + 1)}>Next</button>
</>
);
}
Production Readiness
Global Configuration
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 the data.');
error.info = await res.json();
error.status = res.status;
throw error;
}
return res.json();
};
function App() {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: true,
revalidateOnReconnect: true,
shouldRetryOnError: true,
errorRetryCount: 3,
dedupingInterval: 2000,
focusThrottleInterval: 5000,
onError: (error) => {
console.error('SWR Error:', error);
if (error.status === 401) {
// Redirect to login
}
},
}}
>
<MyApp />
</SWRConfig>
);
}
Error Handling
function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading } = useSWR<User, ApiError>(
`/api/users/${userId}`,
fetcher
);
if (error) {
if (error.status === 404) return <NotFound />;
if (error.status >= 500) return <ServerError />;
return <ErrorMessage error={error} />;
}
if (isLoading) return <Skeleton />;
return <UserCard user={data} />;
}
Monitoring Metrics
| Metric |
Target |
| Cache hit ratio |
> 80% |
| Average response time |
< 200ms |
| Revalidation frequency |
Appropriate for data freshness |
| Error rate |
< 1% |
Checklist
Reference Documentation