SWR Core Knowledge
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:swrfor 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 onClick={handleUpdate}>Update</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 onClick={() => 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
- [ ] Global fetcher configured in
<SWRConfig> - [ ] Error handling for all status codes
- [ ] Loading states for all data fetches
- [ ] Optimistic updates for mutations
- [ ] Conditional fetching where appropriate
- [ ] Stable cache keys (no render-time generation)
- [ ] TypeScript types for all data
- [ ] Retry logic configured
- [ ] Focus revalidation enabled
- [ ] Global error handler for auth failures
微信扫一扫