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>
  );
}

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>
  );
}

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はサーバー状態管理を簡素化し、キャッシュ、再検証、楽観的更新を容易に実装できます。

← 一覧に戻る