TanStack Query (formerly React Query) is the gold standard for server state management. It handles caching, background refetching, pagination, optimistic updates, and more.
Installation
npm install @tanstack/react-query
Setup
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 5 * 60 * 1000, retry: 3 }
}
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<MyApp />
</QueryClientProvider>
);
}
useQuery — Fetch Data
import { useQuery } from "@tanstack/react-query";
function Posts() {
const { data, isLoading, error } = useQuery({
queryKey: ["posts"],
queryFn: async () => {
const res = await fetch("/api/posts");
if (!res.ok) throw new Error("Failed to fetch");
return res.json();
}
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
useMutation — Modify Data
import { useMutation, useQueryClient } from "@tanstack/react-query";
function CreatePost() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (newPost) => {
const res = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newPost)
});
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["posts"] });
}
});
return (
<button onClick={() => mutation.mutate({ title: "New Post", content: "Hello!" })}>
{mutation.isPending ? "Creating..." : "Create Post"}
</button>
);
}
Dependent Queries
function UserPosts({ userId }) {
const user = useQuery({ queryKey: ["user", userId], queryFn: () => fetchUser(userId) });
const posts = useQuery({
queryKey: ["posts", userId],
queryFn: () => fetchPostsByUser(userId),
enabled: !!user.data // Only fetch when user is loaded
});
}
Infinite Scroll
import { useInfiniteQuery } from "@tanstack/react-query";
function InfinitePosts() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ["posts"],
queryFn: ({ pageParam = 0 }) => fetch(`/api/posts?offset=${pageParam}`).then(r => r.json()),
getNextPageParam: (lastPage, pages) => lastPage.nextOffset ?? undefined,
initialPageParam: 0
});
return (
<div>
{data?.pages.flatMap(page => page.posts).map(post => (
<div key={post.id}>{post.title}</div>
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
{isFetchingNextPage ? "Loading..." : "Load More"}
</button>
)}
</div>
);
}
Need to extract or automate web content at scale? Check out my web scraping tools on Apify — no coding required. Or email me at spinov001@gmail.com for custom solutions.
Top comments (0)