Next.js
/
@tanstack/query
// app/api/posts.ts (Server simulation)
function getPosts() {
NextResponse.json([{id: 1}, ...])
}
function likePost(req: NextRequest) {
const { id } = req.body();
const savedPost = db.update({
where: { id },
update: { likes: { inc: 1 } }
});
return savedPost;
}
export { getPosts as GET, savePost as POST }
// app/components/LikeButton.tsx
'use client';
import { useMutation, useQueryClient } from '@tanstack/react-query';
type Post = { id: number; title: string; likes: number };
function likePost(id: string) {
return fetch('/api/posts', {
method: 'POST',
body: JSON.stringify({ id })
})
}
export default function LikeButton({ post }: { post: Post }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (postId: number) => likePost(postId),
onMutate: async (postId) => {
await queryClient.cancelQueries(['posts', postId]);
const previousPost = queryClient.getQueryData<Post>(['posts', postId]);
queryClient.setQueryData(['posts', postId], { ...post, likes: post.likes + 1 });
return { previousPost };
},
onError: (err, postId, context) => {
queryClient.setQueryData(['posts', postId], context?.previousPost);
},
onSuccess: (data) => {
queryClient.setQueryData(['posts', postId], { ...post, likes: data.likes });
},
});
return (
<button onClick={() => mutation.mutate(post.id)}>
Like ({post.likes})
</button>
);
}
// app/posts/page.tsx
'use client';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import LikeButton from '@/app/components/LikeButton';
const queryClient = new QueryClient();
async function fetchPosts(): Promise<Post[]> {
const result = await fetch('/api/posts');
return result.json();
}
export default function PostsPage() {
const { data: posts } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
return (
<QueryClientProvider client={queryClient}>
{posts?.map(post => (
<div key={post.id}>
{post.title} - <LikeButton post={post} />
</div>
))}
</QueryClientProvider>
);
}