TanStack Query (formerly known as React Query) is a powerful data-fetching and caching library for React. It helps manage remote state (like API calls) declaratively, with built-in caching, background sync, pagination, and more — removing the need for useEffect, local loading/error state, and boilerplate fetch logic.
Use it when:
It complements client state managers (like Zustand or Redux), but often replaces them entirely for server state.
NOTE: Tested with the latest version at time of writing, v5.
Fetch server data and handle loading/error state automatically. Cached by queryKey
.
const { data, error, isPending } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(res => res.json())
});
Use useMutation
for POST/PUT/DELETE. Manually trigger and invalidate affected queries after success.
const mutation = useMutation({
mutationFn: (newTodo) => fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo)
}),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] })
});
Trigger a fresh fetch manually or after a mutation.
queryClient.invalidateQueries({ queryKey: ['todos'] }); // mark stale
queryClient.refetchQueries({ queryKey: ['todos'] }); // force refetch now
Fine-tune behavior: when to run, how long to cache, etc.
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId, // don't run until userId exists
staleTime: 1000 * 60 * 5 // 5 minutes before stale
});
Use refetchInterval
to poll at regular intervals.
useQuery({
queryKey: ['metrics'],
queryFn: fetchMetrics,
refetchInterval: 5000 // every 5 seconds
});
Only run if a value exists (e.g. after login or param load).
useQuery({
queryKey: ['account', userId],
queryFn: () => fetchAccount(userId),
enabled: !!userId
});
Fetch paginated data (infinite scroll or cursor-based).
useInfiniteQuery({
queryKey: ['feed'],
queryFn: ({ pageParam }) => fetchFeed({ cursor: pageParam }),
initialPageParam: null,
getNextPageParam: (lastPage) => lastPage.nextCursor
});
Preload data into the cache before it's needed (e.g. on hover).
queryClient.prefetchQuery({
queryKey: ['projects'],
queryFn: fetchProjects
});
Queries are automatically canceled when:
queryKey
changes mid-fetchTrack mutation progress explicitly: pending, error, success.
const { mutate, isPending, isSuccess, isError } = useMutation({
mutationFn: saveSettings
});
Create and provide a QueryClient at app root.
const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
Add optional devtools for live query inspection.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
<ReactQueryDevtools initialIsOpen={false} />
queryKey
: ['posts', postId]
enabled: false
to control execution timingstaleTime
for frequently-read but slow-changing datauseMutation
for any write action, even PUTs