Tanstack Query: Server State Management That Replaces Redux
Redux is overkill for 90% of apps. Most state isn't client state — it's server state: data fetched from an API. Tanstack Query manages server state with caching, background refetching, and optimistic updates.
Install
npm install @tanstack/react-query
npm install --save-dev @tanstack/react-query-devtools
Setup
// app/providers.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes before refetch
retry: 2,
},
},
});
export function Providers({ children }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
);
}
Fetching Data
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
});
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage />;
return <div>{user.name}</div>;
}
Mutations
import { useMutation, useQueryClient } from '@tanstack/react-query';
function UpdateNameForm({ userId }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (name: string) =>
fetch(`/api/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify({ name }),
}).then(r => r.json()),
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['user', userId] });
},
});
return (
<button
onClick={() => mutation.mutate('New Name')}
disabled={mutation.isPending}
>
{mutation.isPending ? 'Saving...' : 'Save'}
</button>
);
}
Optimistic Updates
const toggleTodo = useMutation({
mutationFn: (id: string) => fetch(`/api/todos/${id}/toggle`, { method: 'POST' }),
onMutate: async (id) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previous = queryClient.getQueryData(['todos']);
// Optimistically update
queryClient.setQueryData(['todos'], (old: Todo[]) =>
old.map(t => t.id === id ? { ...t, done: !t.done } : t)
);
return { previous };
},
onError: (err, id, context) => {
// Roll back on error
queryClient.setQueryData(['todos'], context?.previous);
},
});
Infinite Scroll
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 1 }) =>
fetch(`/api/posts?page=${pageParam}`).then(r => r.json()),
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
});
const allPosts = data?.pages.flatMap(p => p.posts) ?? [];
Prefetching
// Prefetch on hover for instant navigation
function PostLink({ postId }) {
const queryClient = useQueryClient();
return (
<Link
href={`/posts/${postId}`}
onMouseEnter={() =>
queryClient.prefetchQuery({
queryKey: ['post', postId],
queryFn: () => getPost(postId),
})
}
>
View Post
</Link>
);
}
Tanstack Query ships pre-configured in the AI SaaS Starter Kit — QueryClient setup, custom hooks for all API calls, devtools enabled in dev. $99 at whoffagents.com.
Top comments (0)