React Query Cheatsheet
React Query is a data-fetching library that makes working with server state in React apps a breeze. Here's a no-fluff cheatsheet for core features, examples, and best practices.
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:
- You're fetching remote data (REST, GraphQL, RPC)
- You want stale-while-revalidate caching, pagination, or background refetches
- You want to avoid manually tracking loading, errors, retries, etc.
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.
Basic Query
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())
});
Mutations
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'] })
});
Invalidate & Refetch
Trigger a fresh fetch manually or after a mutation.
queryClient.invalidateQueries({ queryKey: ['todos'] }); // mark stale
queryClient.refetchQueries({ queryKey: ['todos'] }); // force refetch now
Query Options
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
});
Polling / Intervals
Use refetchInterval to poll at regular intervals.
useQuery({
queryKey: ['metrics'],
queryFn: fetchMetrics,
refetchInterval: 5000 // every 5 seconds
});
Dependent Queries
Only run if a value exists (e.g. after login or param load).
useQuery({
queryKey: ['account', userId],
queryFn: () => fetchAccount(userId),
enabled: !!userId
});
Infinite / Paginated Queries
Fetch paginated data (infinite scroll or cursor-based).
useInfiniteQuery({
queryKey: ['feed'],
queryFn: ({ pageParam }) => fetchFeed({ cursor: pageParam }),
initialPageParam: null,
getNextPageParam: (lastPage) => lastPage.nextCursor
});
Prefetching
Preload data into the cache before it's needed (e.g. on hover).
queryClient.prefetchQuery({
queryKey: ['projects'],
queryFn: fetchProjects
});
Cancellation (Built-In)
Queries are automatically canceled when:
- Component unmounts
queryKeychanges mid-fetch- A new fetch starts before the last finishes
Mutation Status
Track mutation progress explicitly: pending, error, success.
const { mutate, isPending, isSuccess, isError } = useMutation({
mutationFn: saveSettings
});
QueryClient Setup
Create and provide a QueryClient at app root.
const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
Devtools
Add optional devtools for live query inspection.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
<ReactQueryDevtools initialIsOpen={false} />
Best Practices
- Always use array-style
queryKey:['posts', postId] - Use
enabled: falseto control execution timing - Invalidate queries after mutations to sync UI
- Keep queries co-located near the components that use them
- Use
staleTimefor frequently-read but slow-changing data - Prefer
useMutationfor any write action, even PUTs - Wrap all query logic in custom hooks to simplify reuse