data-fetching-patterns
について
このスキルは、モノレポ内でのAPI連携におけるSWRベースのデータ取得とキャッシュパターンを提供します。カスタムフックの作成、ローディング/エラー状態の処理、モックデータの操作時にご利用ください。SWR設定、useUserInfoのようなカスタムフックパターン、エラー処理の実装について網羅しています。
クイックインストール
Claude Code
推奨/plugin add https://github.com/lsst-sqre/squareonegit clone https://github.com/lsst-sqre/squareone.git ~/.claude/skills/data-fetching-patternsこのコマンドをClaude Codeにコピー&ペーストしてスキルをインストールします
ドキュメント
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>
GitHub リポジトリ
関連スキル
content-collections
メタThis skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.
creating-opencode-plugins
メタThis skill provides the structure and API specifications for creating OpenCode plugins that hook into 25+ event types like commands, files, and LSP operations. It offers implementation patterns for JavaScript/TypeScript modules that intercept and extend the AI assistant's lifecycle. Use it when you need to build event-driven plugins for monitoring, custom handling, or extending OpenCode's capabilities.
evaluating-llms-harness
テストThis 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.
polymarket
メタThis skill enables developers to build applications with the Polymarket prediction markets platform, including API integration for trading and market data. It also provides real-time data streaming via WebSocket to monitor live trades and market activity. Use it for implementing trading strategies or creating tools that process live market updates.
