TanStack Query完全ガイド
TanStack Query完全ガイド
Section titled “TanStack Query完全ガイド”TanStack Query(旧React Query)を使用したサーバー状態管理を、実務で使える実装例とベストプラクティスとともに詳しく解説します。
1. TanStack Queryとは
Section titled “1. TanStack Queryとは”TanStack Queryの特徴
Section titled “TanStack Queryの特徴”TanStack Queryは、サーバー状態管理のためのライブラリです。データフェッチング、キャッシング、同期、更新を簡単に実現します。
TanStack Queryの特徴 ├─ 自動キャッシング ├─ バックグラウンド更新 ├─ オプティミスティック更新 └─ エラーハンドリングなぜTanStack Queryが必要か
Section titled “なぜTanStack Queryが必要か”問題のある構成(TanStack Queryなし):
// 問題: 手動でのデータフェッチング管理function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { setLoading(true); fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => { setUser(data); setLoading(false); }) .catch(err => { setError(err); setLoading(false); }); }, [userId]);
if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return <div>{user?.name}</div>;}
// 問題点:// 1. キャッシングがない// 2. 再取得のロジックが必要// 3. エラーハンドリングが複雑// 4. ローディング状態の管理が複雑解決: TanStack Queryによる簡潔な実装
// 解決: TanStack Queryによる簡潔な実装import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) { const { data: user, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), });
if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return <div>{user?.name}</div>;}
// メリット:// 1. 自動キャッシング// 2. 自動再取得// 3. エラーハンドリング// 4. ローディング状態の管理2. インストール
Section titled “2. インストール”基本的なインストール
Section titled “基本的なインストール”npm install @tanstack/react-queryDevToolsのインストール
Section titled “DevToolsのインストール”npm install @tanstack/react-query-devtools3. QueryClientの設定
Section titled “3. QueryClientの設定”基本的な設定
Section titled “基本的な設定”import { QueryClient, QueryClientProvider } from '@tanstack/react-query';import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient();
function App() { return ( <QueryClientProvider client={queryClient}> <YourApp /> <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> );}カスタム設定
Section titled “カスタム設定”import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5分 cacheTime: 10 * 60 * 1000, // 10分 retry: 3, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), }, },});
function App() { return ( <QueryClientProvider client={queryClient}> <YourApp /> </QueryClientProvider> );}4. データフェッチング
Section titled “4. データフェッチング”基本的なクエリ
Section titled “基本的なクエリ”import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) { const { data, isLoading, error, isError } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), });
if (isLoading) return <div>Loading...</div>; if (isError) return <div>Error: {error.message}</div>; return <div>{data?.name}</div>;}条件付きクエリ
Section titled “条件付きクエリ”// enabledオプションで条件付き実行function UserPosts({ userId }) { const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), });
const { data: posts } = useQuery({ queryKey: ['posts', userId], queryFn: () => fetchUserPosts(userId), enabled: !!user, // userが存在する場合のみ実行 });
return <div>{posts?.map(post => <div key={post.id}>{post.title}</div>)}</div>;}// 依存クエリの実装function UserDashboard({ userId }) { const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), });
const { data: posts } = useQuery({ queryKey: ['posts', userId], queryFn: () => fetchUserPosts(userId), enabled: !!user, });
const { data: comments } = useQuery({ queryKey: ['comments', userId], queryFn: () => fetchUserComments(userId), enabled: !!user && !!posts, });
return ( <div> <div>User: {user?.name}</div> <div>Posts: {posts?.length}</div> <div>Comments: {comments?.length}</div> </div> );}5. ミューテーション
Section titled “5. ミューテーション”基本的なミューテーション
Section titled “基本的なミューテーション”import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() { const queryClient = useQueryClient();
const mutation = useMutation({ mutationFn: createUser, onSuccess: () => { // クエリの無効化 queryClient.invalidateQueries({ queryKey: ['users'] }); }, });
return ( <button onClick={() => mutation.mutate({ name: 'John', email: 'john@example.com' })} disabled={mutation.isPending} > {mutation.isPending ? 'Creating...' : 'Create User'} </button> );}オプティミスティック更新
Section titled “オプティミスティック更新”// オプティミスティック更新の実装function UpdateUser({ userId }) { const queryClient = useQueryClient();
const mutation = useMutation({ mutationFn: updateUser, onMutate: async (newUser) => { // 進行中のクエリをキャンセル await queryClient.cancelQueries({ queryKey: ['user', userId] });
// スナップショットを取得 const previousUser = queryClient.getQueryData(['user', userId]);
// オプティミスティック更新 queryClient.setQueryData(['user', userId], newUser);
return { previousUser }; }, onError: (err, newUser, context) => { // エラー時はロールバック queryClient.setQueryData(['user', userId], context.previousUser); }, onSettled: () => { // 最終的にサーバーから再取得 queryClient.invalidateQueries({ queryKey: ['user', userId] }); }, });
return ( <button onClick={() => mutation.mutate({ id: userId, name: 'New Name' })}> Update User </button> );}6. 実務でのベストプラクティス
Section titled “6. 実務でのベストプラクティス”パターン1: カスタムフックの作成
Section titled “パターン1: カスタムフックの作成”import { useQuery } from '@tanstack/react-query';
export function useUser(userId) { return useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), enabled: !!userId, });}
// 使用function UserProfile({ userId }) { const { data: user, isLoading } = useUser(userId); if (isLoading) return <div>Loading...</div>; return <div>{user?.name}</div>;}パターン2: クエリキーの管理
Section titled “パターン2: クエリキーの管理”export const queryKeys = { users: { all: ['users'] as const, lists: () => [...queryKeys.users.all, 'list'] as const, list: (filters: string) => [...queryKeys.users.lists(), { filters }] as const, details: () => [...queryKeys.users.all, 'detail'] as const, detail: (id: number) => [...queryKeys.users.details(), id] as const, },};
// 使用function UserList() { const { data } = useQuery({ queryKey: queryKeys.users.lists(), queryFn: fetchUsers, }); return <div>{data?.map(user => <div key={user.id}>{user.name}</div>)}</div>;}パターン3: エラーハンドリング
Section titled “パターン3: エラーハンドリング”// グローバルエラーハンドラーconst queryClient = new QueryClient({ defaultOptions: { queries: { onError: (error) => { // グローバルエラーハンドリング console.error('Query error:', error); // エラー通知など }, }, mutations: { onError: (error) => { // グローバルエラーハンドリング console.error('Mutation error:', error); }, }, },});7. よくある問題と解決策
Section titled “7. よくある問題と解決策”問題1: キャッシュが更新されない
Section titled “問題1: キャッシュが更新されない”原因:
- クエリキーが一致していない
- 無効化が実行されていない
解決策:
// クエリキーの一貫性を保つconst queryKeys = { users: { all: ['users'] as const, detail: (id: number) => ['users', id] as const, },};
// ミューテーション後の無効化const mutation = useMutation({ mutationFn: updateUser, onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.users.all }); },});問題2: 不要な再取得
Section titled “問題2: 不要な再取得”原因:
- staleTimeが短すぎる
- キャッシュ設定が不適切
解決策:
// staleTimeを適切に設定const { data } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, // 5分間は再取得しない});これで、TanStack Queryの基礎知識と実務での使い方を理解できるようになりました。