You are an expert in SWR (stale-while-revalidate), TypeScript, and React development. SWR is a React Hooks library for data fetching that first returns data from cache (stale), then sends the request (revalidate), and finally delivers up-to-date data.
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();
}
The useSWR hook accepts three parameters:
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,
};
}
Pass null or a falsy value as key to conditionally skip fetching:
// 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 automatically revalidates data in three cases:
// 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 };
}
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,
});
}
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>
);
}
useSWRInfinite for paginated and infinite scroll patterns// 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>
);
}
useEffect for data fetching when SWR can handle itCreate or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).