data-fetching-patterns
About
This skill provides SWR-based data fetching and caching patterns for API interactions in the monorepo. Use it when creating custom hooks, handling loading/error states, or working with mock data. It covers SWR configuration, custom hook patterns like useUserInfo, and error handling implementations.
Documentation
Data Fetching Patterns
The monorepo uses SWR (stale-while-revalidate) for data fetching and caching.
SWR Basics
Simple Usage
import useSWR from 'swr';
function MyComponent() {
const { data, error, isLoading } = useSWR('/api/data', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data.value}</div>;
}
Fetcher Function
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
import useSWR from 'swr';
type UserData = {
username: string;
email: string;
groups: string[];
};
export function useUserInfo() {
const { data, error, isLoading } = useSWR<UserData>(
'/auth/api/v1/user-info',
fetcher
);
return {
user: data,
isLoading,
error,
};
}
With Conditional Fetching
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
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
function MyComponent() {
const { data, error, isLoading } = useUserInfo();
if (isLoading) {
return <LoadingSpinner />;
}
if (error) {
return (
<ErrorMessage
title="Failed to load user data"
message={error.message}
retry={() => window.location.reload()}
/>
);
}
if (!data) {
return <EmptyState message="No data available" />;
}
return <UserProfile user={data} />;
}
Global Error Handler
// _app.tsx
import { SWRConfig } from 'swr';
function MyApp({ Component, pageProps }) {
return (
<SWRConfig
value={{
fetcher,
onError: (error, key) => {
console.error('SWR Error:', key, error);
// Send to error tracking
},
}}
>
<Component {...pageProps} />
</SWRConfig>
);
}
Mutations
Optimistic Updates
function useUpdateUser() {
const { data, mutate } = useSWR('/api/user', fetcher);
const updateUser = async (updates: Partial<UserData>) => {
// 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
function MyComponent() {
const { data, mutate } = useSWR('/api/data', fetcher);
const handleRefresh = () => {
mutate(); // Revalidate
};
const handleUpdate = async () => {
await updateData();
mutate(); // Revalidate after update
};
return (
<div>
<button onClick={handleRefresh}>Refresh</button>
{data && <DataDisplay data={data} />}
</div>
);
}
Caching and Revalidation
SWR Configuration
<SWRConfig
value={{
refreshInterval: 30000, // Revalidate every 30s
revalidateOnFocus: true, // Revalidate when window regains focus
revalidateOnReconnect: true, // Revalidate on network recovery
dedupingInterval: 2000, // Dedupe requests within 2s
}}
>
<App />
</SWRConfig>
Per-Hook Configuration
const { data } = useSWR('/api/data', fetcher, {
refreshInterval: 10000, // Override global setting
revalidateOnFocus: false,
});
Mock Data
Development Mocks
// src/lib/mocks/userData.ts
export const mockUserData = {
username: 'testuser',
email: '[email protected]',
groups: ['admin', 'developers'],
uid: 1000,
};
export const mockUserDataError = {
error: 'Unauthorized',
message: 'Invalid credentials',
};
Mock API Routes
// 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
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
function MyComponent() {
const { data, isLoading } = useData();
if (isLoading) {
return (
<div>
<Skeleton width="100%" height={60} />
<Skeleton width="80%" height={40} />
<Skeleton width="90%" height={40} />
</div>
);
}
return <DataDisplay data={data} />;
}
Suspense (Experimental)
import { Suspense } from 'react';
function MyComponent() {
const { data } = useSWR('/api/data', fetcher, {
suspense: true, // Enable Suspense
});
return <DataDisplay data={data} />;
}
// Usage
<Suspense fallback={<LoadingSpinner />}>
<MyComponent />
</Suspense>
Pagination
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 (
<div>
{isLoading ? <LoadingSpinner /> : <List items={data} />}
<Pagination
current={page}
total={total}
onChange={setPage}
/>
</div>
);
}
Infinite Loading
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
- Create custom hooks for each API endpoint
- Handle all states (loading, error, empty)
- Use TypeScript types for data
- Mock APIs for development
- Configure revalidation appropriately
- Use optimistic updates for better UX
- Dedupe requests with SWR's built-in caching
- Handle authentication in fetcher
- Log errors for debugging
- Test with mock data
Common Patterns
Dependent Fetching
function useUserAndPosts() {
const { data: user } = useUserInfo();
const { data: posts } = useSWR(
user ? `/api/users/${user.id}/posts` : null,
fetcher
);
return { user, posts };
}
Polling
const { data } = useSWR('/api/status', fetcher, {
refreshInterval: 1000, // Poll every second
});
Prefetching
import { mutate } from 'swr';
function prefetchData() {
mutate('/api/data', fetcher('/api/data'));
}
// Prefetch on hover
<Link onMouseEnter={() => prefetchData('/api/page-data')}>
Page
</Link>
Quick Install
/plugin add https://github.com/lsst-sqre/squareone/tree/main/data-fetching-patternsCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
llamaindex
MetaLlamaIndex is a data framework for building RAG-powered LLM applications, specializing in document ingestion, indexing, and querying. It provides key features like vector indices, query engines, and agents, and supports over 300 data connectors. Use it for document Q&A, chatbots, and knowledge retrieval when building data-centric applications.
csv-data-summarizer
MetaThis skill automatically analyzes CSV files to generate comprehensive statistical summaries and visualizations using Python's pandas and matplotlib/seaborn. It should be triggered whenever a user uploads or references CSV data without prompting for analysis preferences. The tool provides immediate insights into data structure, quality, and patterns through automated analysis and visualization.
