TanStack Query入門 - データフェッチング

intermediate | 45分 で読める | 2025.12.06

公式ドキュメント

このチュートリアルで学ぶこと

  • TanStack Queryの基本
  • useQueryでデータ取得
  • useMutationで更新
  • キャッシュと再検証
  • 楽観的更新

Step 1: セットアップ

npm install @tanstack/react-query
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
 return (
 <QueryClientProvider client={queryClient}>
 {children}
 <ReactQueryDevtools />
 </QueryClientProvider>
 );
}

ポイント: useQueryはデータフェッチング・キャッシュ・再検証を自動管理します。queryKeyでキャッシュを一意に識別するため、キー設計が重要です

Step 2: 基本的なクエリ

import { useQuery } from '@tanstack/react-query';

function UserList() {
 const { data, isLoading, error } = useQuery({
 queryKey: ['users'],
 queryFn: async () => {
 const res = await fetch('/api/users');
 return res.json();
 },
 });

 if (isLoading) return <div>Loading...</div>;
 if (error) return <div>Error: {error.message}</div>;

 return (
 <ul>
 {data.map((user: any) => (
 <li key={user.id}>{user.name}</li>
 ))}
 </ul>
 );
}

Step 3: パラメータ付きクエリ

function UserDetail({ userId }: { userId: string }) {
 const { data, isLoading } = useQuery({
 queryKey: ['users', userId],
 queryFn: async () => {
 const res = await fetch(`/api/users/${userId}`);
 return res.json();
 },
 enabled: !!userId, // userIdがあるときのみ実行
 });

 if (isLoading) return <div>Loading...</div>;

 return <div>{data?.name}</div>;
}

Step 4: Mutation

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateUserForm() {
 const queryClient = useQueryClient();

 const mutation = useMutation({
 mutationFn: async (newUser: { name: string; email: string }) => {
 const res = await fetch('/api/users', {
 method: 'POST',
 body: JSON.stringify(newUser),
 });
 return res.json();
 },
 onSuccess: () => {
 // キャッシュを無効化して再取得
 queryClient.invalidateQueries({ queryKey: ['users'] });
 },
 });

 const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
 e.preventDefault();
 const formData = new FormData(e.currentTarget);
 mutation.mutate({
 name: formData.get('name') as string,
 email: formData.get('email') as string,
 });
 };

 return (
 <form onSubmit={handleSubmit}>
 <input name="name" placeholder="Name" />
 <input name="email" placeholder="Email" />
 <button type="submit" disabled={mutation.isPending}>
 {mutation.isPending ? 'Creating...' : 'Create User'}
 </button>
 </form>
 );
}

実践メモ: 楽観的更新を使うと、サーバーの応答を待たずにUIを即座に更新できます。ユーザー体験が大幅に向上しますが、エラー時のロールバック処理を必ず実装しましょう。

Step 5: 楽観的更新

const mutation = useMutation({
 mutationFn: updateUser,
 onMutate: async (newData) => {
 await queryClient.cancelQueries({ queryKey: ['users', newData.id] });

 const previousUser = queryClient.getQueryData(['users', newData.id]);

 queryClient.setQueryData(['users', newData.id], newData);

 return { previousUser };
 },
 onError: (err, newData, context) => {
 queryClient.setQueryData(['users', newData.id], context?.previousUser);
 },
 onSettled: (data, error, variables) => {
 queryClient.invalidateQueries({ queryKey: ['users', variables.id] });
 },
});

Step 6: 無限スクロール

import { useInfiniteQuery } from '@tanstack/react-query';

function InfiniteList() {
 const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
 useInfiniteQuery({
 queryKey: ['posts'],
 queryFn: async ({ pageParam = 0 }) => {
 const res = await fetch(`/api/posts?cursor=${pageParam}`);
 return res.json();
 },
 getNextPageParam: (lastPage) => lastPage.nextCursor,
 });

 return (
 <div>
 {data?.pages.map((page) =>
 page.items.map((item: any) => <div key={item.id}>{item.title}</div>)
 )}
 <button
 onClick={() => fetchNextPage()}
 disabled={!hasNextPage || isFetchingNextPage}
 >
 {isFetchingNextPage ? 'Loading...' : 'Load More'}
 </button>
 </div>
 );
}

サーバー状態として考える

TanStack Queryは、フォーム入力のようなローカル状態ではなく、APIから取得するサーバー状態を扱うためのライブラリです。キャッシュ、再取得、ローディング、エラー、楽観的更新をまとめて管理できる点が強みです。

重要なのはqueryKeyの設計です。同じデータは同じキーで取得し、ユーザーIDや検索条件など結果に影響する値はキーに含めます。キーが曖昧だと、古いデータが表示されたり、不要な再取得が増えたりします。

導入時の判断

  • APIデータを複数画面で使い回すか
  • ローディングやエラー表示を統一したいか
  • 更新後にどのデータを再取得するか決めているか
  • キャッシュ時間と鮮度のバランスが合っているか

まとめ

注意: staleTimeとgcTimeの設定を適切に行わないと、不要なリフェッチが多発したりメモリリークの原因になります

TanStack Queryはサーバー状態管理を簡素化し、キャッシュ、再検証、楽観的更新を容易に実装できます。

参考リソース

← 一覧に戻る
PR
PR
PR
PR