このチュートリアルで学ぶこと
✓ 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はサーバー状態管理を簡素化し、キャッシュ、再検証、楽観的更新を容易に実装できます。
← 一覧に戻る