--- name: data-fetching-patterns description: SWR-based data fetching and caching patterns used throughout the monorepo. Use this skill when implementing API interactions, creating custom data hooks, handling loading/error states, or working with mock data. Covers SWR configuration, custom hook patterns (useUserInfo, useTimesSquarePage), error handling, and mock data setup. --- # Data Fetching Patterns The monorepo uses SWR (stale-while-revalidate) for data fetching and caching. ## SWR Basics ### Simple Usage ```typescript import useSWR from 'swr'; function MyComponent() { const { data, error, isLoading } = useSWR('/api/data', fetcher); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return
{data.value}
; } ``` ### Fetcher Function ```typescript const fetcher = (url: string) => fetch(url).then(res => res.json()); // Or with error handling 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(); }; ``` ## Custom Hook Pattern ### Basic Pattern ```typescript import useSWR from 'swr'; type UserData = { username: string; email: string; groups: string[]; }; export function useUserInfo() { const { data, error, isLoading } = useSWR( '/auth/api/v1/user-info', fetcher ); return { user: data, isLoading, error, }; } ``` ### With Conditional Fetching ```typescript export function useTimesSquarePage(pageSlug: string | null) { const { data, error, isLoading } = useSWR( pageSlug ? `/times-square/api/v1/pages/${pageSlug}` : null, fetcher ); return { page: data, isLoading, error, }; } ``` ### With Parameters ```typescript export function usePageData(id: string, options?: { refreshInterval?: number }) { const { data, error, isLoading, mutate } = useSWR( `/api/pages/${id}`, fetcher, { refreshInterval: options?.refreshInterval, } ); return { data, isLoading, error, refresh: mutate, // Manual revalidation }; } ``` ## Error Handling ### In Components ```typescript function MyComponent() { const { data, error, isLoading } = useUserInfo(); if (isLoading) { return ; } if (error) { return ( window.location.reload()} /> ); } if (!data) { return ; } return ; } ``` ### Global Error Handler ```typescript // _app.tsx import { SWRConfig } from 'swr'; function MyApp({ Component, pageProps }) { return ( { console.error('SWR Error:', key, error); // Send to error tracking }, }} > ); } ``` ## Mutations ### Optimistic Updates ```typescript function useUpdateUser() { const { data, mutate } = useSWR('/api/user', fetcher); const updateUser = async (updates: Partial) => { // Optimistic update mutate( { ...data, ...updates }, false // Don't revalidate yet ); try { // Make API call const updated = await fetch('/api/user', { method: 'PATCH', body: JSON.stringify(updates), }).then(res => res.json()); // Revalidate with real data mutate(updated); } catch (error) { // Rollback on error mutate(); throw error; } }; return { data, updateUser }; } ``` ### Revalidation ```typescript function MyComponent() { const { data, mutate } = useSWR('/api/data', fetcher); const handleRefresh = () => { mutate(); // Revalidate }; const handleUpdate = async () => { await updateData(); mutate(); // Revalidate after update }; return (
{data && }
); } ``` ## Caching and Revalidation ### SWR Configuration ```typescript ``` ### Per-Hook Configuration ```typescript const { data } = useSWR('/api/data', fetcher, { refreshInterval: 10000, // Override global setting revalidateOnFocus: false, }); ``` ## Mock Data ### Development Mocks ```typescript // src/lib/mocks/userData.ts export const mockUserData = { username: 'testuser', email: 'test@example.com', groups: ['admin', 'developers'], uid: 1000, }; export const mockUserDataError = { error: 'Unauthorized', message: 'Invalid credentials', }; ``` ### Mock API Routes ```typescript // pages/api/dev/user-info.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { mockUserData } from '../../../src/lib/mocks/userData'; export default function handler( req: NextApiRequest, res: NextApiResponse ) { // Simulate delay setTimeout(() => { res.status(200).json(mockUserData); }, 500); } ``` ### Conditional Mocking ```typescript const fetcher = async (url: string) => { // Use mock in development if (process.env.NODE_ENV === 'development' && url.startsWith('/api/dev/')) { return fetch(url).then(res => res.json()); } // Use real API in production return fetch(url).then(res => res.json()); }; ``` ## Loading States ### Skeleton Loaders ```typescript function MyComponent() { const { data, isLoading } = useData(); if (isLoading) { return (
); } return ; } ``` ### Suspense (Experimental) ```typescript import { Suspense } from 'react'; function MyComponent() { const { data } = useSWR('/api/data', fetcher, { suspense: true, // Enable Suspense }); return ; } // Usage }> ``` ## Pagination ```typescript function usePaginatedData(page: number, pageSize: number) { const { data, error, isLoading } = useSWR( `/api/data?page=${page}&size=${pageSize}`, fetcher ); return { data: data?.items || [], total: data?.total || 0, isLoading, error, }; } function PaginatedList() { const [page, setPage] = useState(1); const { data, total, isLoading } = usePaginatedData(page, 10); return (
{isLoading ? : }
); } ``` ## Infinite Loading ```typescript import useSWRInfinite from 'swr/infinite'; function useInfiniteData() { const getKey = (pageIndex: number, previousPageData: any) => { if (previousPageData && !previousPageData.hasMore) return null; return `/api/data?page=${pageIndex}`; }; const { data, size, setSize, isLoading } = useSWRInfinite( getKey, fetcher ); const items = data ? data.flatMap(page => page.items) : []; const hasMore = data ? data[data.length - 1]?.hasMore : false; return { items, isLoading, hasMore, loadMore: () => setSize(size + 1), }; } ``` ## Best Practices 1. **Create custom hooks** for each API endpoint 2. **Handle all states** (loading, error, empty) 3. **Use TypeScript types** for data 4. **Mock APIs** for development 5. **Configure revalidation** appropriately 6. **Use optimistic updates** for better UX 7. **Dedupe requests** with SWR's built-in caching 8. **Handle authentication** in fetcher 9. **Log errors** for debugging 10. **Test with mock data** ## Common Patterns ### Dependent Fetching ```typescript function useUserAndPosts() { const { data: user } = useUserInfo(); const { data: posts } = useSWR( user ? `/api/users/${user.id}/posts` : null, fetcher ); return { user, posts }; } ``` ### Polling ```typescript const { data } = useSWR('/api/status', fetcher, { refreshInterval: 1000, // Poll every second }); ``` ### Prefetching ```typescript import { mutate } from 'swr'; function prefetchData() { mutate('/api/data', fetcher('/api/data')); } // Prefetch on hover prefetchData('/api/page-data')}> Page ```