react-query
ReactアプリケーションにおけるReact Queryを活用したデータ取得、キャッシュ管理、サーバー状態の同期に関するガイドラインを提供します。React Queryの導入や実装方法について質問・相談する際に活用できます。
description の原文を見る
Guidelines for using React Query for data fetching, caching, and server state synchronization in React applications
SKILL.md 本文
React Query ベストプラクティス
React Query、TypeScript、React 開発のエキスパートです。React Query(現在は TanStack Query)は、組み込みキャッシング、バックグラウンド更新、古いデータ管理を備えたデータ取得ロジックを簡素化します。
コア原則
- すべてのデータ取得とキャッシングに React Query を使用する
- サーバーデータに対して
useStateの代わりに React Query の組み込み状態管理を活用する - クライアント側のグローバル状態管理には React Context と
useReducerを使用する - 適切なキャッシング戦略により過剰な API 呼び出しを避ける
- 常にローディング状態とエラーを適切に処理する
プロジェクト構造
src/
components/
[Feature]/
index.tsx
queries.ts # Feature-specific query hooks
mutations.ts # Feature-specific mutation hooks
hooks/
useAuth.ts
useApi.ts
services/
api/
client.ts # Axios/fetch configuration
users.ts # User API functions
posts.ts # Post API functions
providers/
ReactQueryProvider.tsx
types/
index.ts
セットアップ
プロバイダー設定
// providers/ReactQueryProvider.tsx
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
retry: 2,
refetchOnWindowFocus: true,
},
},
});
export function ReactQueryProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
);
}
クエリパターン
基本的なクエリフック
import { useQuery } from 'react-query';
import { fetchUser, User } from '@/services/api/users';
export function useUser(userId: string) {
return useQuery<User, Error>(
['user', userId],
() => fetchUser(userId),
{
enabled: !!userId,
staleTime: 1000 * 60 * 10, // 10 minutes
}
);
}
エラーハンドリング付きクエリ
サービスは React Query がキャッチして表示できるユーザーフレンドリーなエラーをスローする必要があります:
// services/api/users.ts
export async function fetchUser(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
// Throw user-friendly error message
throw new Error('Unable to load user profile. Please try again.');
}
return response.json();
}
// Component usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error.message} />;
return <ProfileCard user={user} />;
}
依存関係のあるクエリ
function useUserWithPosts(userId: string) {
const userQuery = useUser(userId);
const postsQuery = useQuery(
['posts', userId],
() => fetchUserPosts(userId),
{
enabled: !!userQuery.data,
}
);
return { userQuery, postsQuery };
}
ページネーションクエリ
function usePaginatedUsers(page: number, limit: number = 10) {
return useQuery(
['users', 'list', { page, limit }],
() => fetchUsers({ page, limit }),
{
keepPreviousData: true,
}
);
}
無限スクロール
import { useInfiniteQuery } from 'react-query';
function useInfiniteUsers() {
return useInfiniteQuery(
['users', 'infinite'],
({ pageParam = 1 }) => fetchUsers({ page: pageParam }),
{
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
}
);
}
ミューテーションパターン
基本的なミューテーション
import { useMutation, useQueryClient } from 'react-query';
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation(createUser, {
onSuccess: () => {
queryClient.invalidateQueries(['users']);
},
onError: (error: Error) => {
toast.error(error.message);
},
});
}
楽観的更新
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation(updateUser, {
onMutate: async (updatedUser) => {
await queryClient.cancelQueries(['user', updatedUser.id]);
const previousUser = queryClient.getQueryData(['user', updatedUser.id]);
queryClient.setQueryData(['user', updatedUser.id], updatedUser);
return { previousUser };
},
onError: (err, updatedUser, context) => {
if (context?.previousUser) {
queryClient.setQueryData(['user', updatedUser.id], context.previousUser);
}
},
onSettled: (data, error, updatedUser) => {
queryClient.invalidateQueries(['user', updatedUser.id]);
},
});
}
状態管理の統合
Context/Reducer との組み合わせ
React Query をサーバー状態に、Context/Reducer をクライアント状態に使用します:
// Client state with Context
const AppStateContext = createContext<AppState | undefined>(undefined);
const AppDispatchContext = createContext<Dispatch<Action> | undefined>(undefined);
function AppProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
}
// Server state with React Query
function UserDashboard() {
const { theme } = useAppState(); // Client state
const { data: user } = useUser(userId); // Server state
return <Dashboard theme={theme} user={user} />;
}
Zustand との組み合わせ(代替案)
import { create } from 'zustand';
// Client state store
const useStore = create((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
}));
// Component using both
function App() {
const theme = useStore((state) => state.theme);
const { data: user } = useUser(userId);
return <Layout theme={theme} user={user} />;
}
パフォーマンス最適化
クエリキーのベストプラクティス
// Structured query keys
const queryKeys = {
users: {
all: ['users'] as const,
lists: () => [...queryKeys.users.all, 'list'] as const,
list: (filters: Filters) => [...queryKeys.users.lists(), filters] as const,
details: () => [...queryKeys.users.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.users.details(), id] as const,
},
};
選別されたサブスクリプション
// Only subscribe to user name changes
function useUserName(userId: string) {
return useUser(userId, {
select: (user) => user.name,
});
}
プリフェッチング
function UserListItem({ userId }: { userId: string }) {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
queryClient.prefetchQuery(
['user', userId],
() => fetchUser(userId),
{ staleTime: 60000 }
);
};
return (
<li onMouseEnter={handleMouseEnter}>
<Link to={`/users/${userId}`}>View Profile</Link>
</li>
);
}
エラーハンドリングパターン
グローバルエラーハンドラー
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error: Error) => {
console.error('Query error:', error);
},
},
mutations: {
onError: (error: Error) => {
toast.error(error.message);
},
},
},
});
エラーバウンダリー
import { QueryErrorResetBoundary } from 'react-query';
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<p>Something went wrong: {error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)}
>
<UserProfile />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
主要な慣例
- React Query DevTools を使用してキャッシュを検査しクエリステータスを追跡する
- react-query フックを機能別ディレクトリにグループ化する(機能ベースの組織)
- 常にエラーを適切に処理し、ユーザーフレンドリーなメッセージと再試行オプションを提供する
- 必要なデータのみを取得する - API パラメータを使用してデータ転送を削減する
- クエリの深いネストを避ける - パフォーマンス向上のためにフラット化する
- コンポーネント固有のデータにはローカル状態を、共有データにはグローバル状態を使用する
- React Query の組み込みキャッシングと状態管理機能を活用する
避けるべきアンチパターン
- データ取得に
useEffectを使用しない - サーバーデータを
useStateに保存しない - ローディングとエラー状態の処理を忘れない
- 適切なキャッシュ無効化戦略のないクエリを作成しない
- 条件付きクエリの
enabledオプションをスキップしない - クエリレスポンスの TypeScript 型を無視しない
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mindrally
- リポジトリ
- mindrally/skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/mindrally/skills / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。