⏱️ Estimated read: 3 min
|
|
Installation
npm install @tanstack/react-query
# or
pnpm add @tanstack/react-query
# or
yarn add @tanstack/react-query
| Package | Command | Purpose |
|---|
| Core | npm install @tanstack/react-query | Required |
| DevTools | npm install -D @tanstack/react-query-devtools | Development only |
| ESLint plugin | npm install -D @tanstack/eslint-plugin-query | Optional quality |
| Requirement | Value |
|---|
| React version | 18.0+ (v5) |
| v4 for React 17 | npm install @tanstack/react-query@4 |
| v5 hook syntax | Object only: useQuery({ queryKey, queryFn }) |
| v4 → v5 rename | cacheTime → gcTime |
| v4 → v5 flag | isLoading → isPending for initial state |
Setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 60,
retry: 1,
refetchOnWindowFocus: false,
},
},
})
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
useQuery
const { data, isPending, isError, error, isFetching, refetch } = useQuery({
queryKey: ['todos', { status }],
queryFn: async () => {
const res = await fetch(`/api/todos?status=${status}`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
},
staleTime: 30_000,
enabled: !!status,
placeholderData: keepPreviousData,
select: (todos) => todos.filter((t) => !t.done),
})
| Option | Purpose |
|---|
queryKey | Cache address — include every queryFn dependency |
queryFn | Must return a promise and throw on HTTP errors |
staleTime | How long data is fresh (default 0) |
gcTime | How long inactive data is kept (default 5 min) |
enabled | Defer the fetch until condition is true |
placeholderData | Keep previous data while fetching |
select | Transform data before returning |
refetchInterval | Poll on an interval |
| Flag | Meaning |
|---|
isPending | No data yet |
isSuccess | Data available |
isError | Query function threw |
isFetching | Any fetch running (incl. background) |
isPlaceholderData | Data is from previous key |
useMutation
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: async (newTodo) => {
const res = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTodo),
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
},
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
onError: (error) => console.error(error.message),
})
mutation.mutate({ title: 'New todo' })
| Option / Result | Purpose |
|---|
mutationFn | Promise-returning write function |
onMutate | Before request — for optimistic updates |
onSuccess | After success — for invalidation |
onError | After failure — for rollback |
onSettled | After success or failure |
mutate(vars) | Fire-and-forget trigger |
mutateAsync(vars) | Promise-returning trigger |
isPending | Request running |
reset() | Clear mutation state |
Cache Management
queryClient.invalidateQueries({ queryKey: ['todos'] })
queryClient.invalidateQueries({ queryKey: ['todos'], exact: true })
queryClient.invalidateQueries({ queryKey: ['todos'], refetchType: 'none' })
queryClient.setQueryData(['todos', id], updated)
queryClient.removeQueries({ queryKey: ['todos', id] })
| Method | Purpose |
|---|
invalidateQueries | Mark stale and refetch active |
setQueryData | Write directly to cache |
getQueryData | Read from cache |
removeQueries | Delete cache entry |
resetQueries | Reset to initial state |
cancelQueries | Cancel in-flight request |
refetchType | Behavior |
|---|
'active' | Refetch active only (default) |
'inactive' | Refetch inactive only |
'all' | Refetch both |
'none' | Mark stale only |
useInfiniteQuery
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam }) => fetchPosts(pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
const allPosts = data?.pages.flatMap((page) => page.posts) ?? []
| Item | Meaning |
|---|
data.pages | Array of fetched pages |
data.pageParams | Array of parameters |
fetchNextPage | Load next page |
hasNextPage | Whether next page exists |
isFetchingNextPage | Next-page fetch running |
initialPageParam | First param (v5 required) |
getNextPageParam | Next param or undefined |
maxPages | Limit pages in memory |
useQueries (Parallel)
const results = useQueries({
queries: ids.map((id) => ({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
})),
combine: (results) => ({
total: results.reduce((sum, r) => sum + (r.data?.count ?? 0), 0),
isLoading: results.some((r) => r.isPending),
}),
})
Testing
export function createTestQueryClient() {
return new QueryClient({
defaultOptions: { queries: { retry: false, gcTime: Infinity } },
logger: { log: console.log, warn: console.warn, error: () => {} },
})
}
export function createWrapper() {
const queryClient = createTestQueryClient()
return ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
}
const { result } = renderHook(() => useTodos(), { wrapper: createWrapper() })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
Defaults Reference
| Option | Default |
|---|
staleTime | 0 |
gcTime | 5 minutes |
retry | 3 |
refetchOnWindowFocus | true |
refetchOnReconnect | true |
Common Pitfalls
| Pitfall | Fix |
|---|
| Query refetches constantly | Raise staleTime above 0 |
| Stale data displayed | Add the dependency to queryKey |
| Cache bleeds between tests | Fresh QueryClient per test |
| Error test times out | retry: false in test client |
| Mutation does not refresh query | Add invalidateQueries in onSuccess |
| Infinite query has one page | getNextPageParam must return a cursor |
isLoading used for background refetch | Use isFetching instead |
Quick How-To Guides & Fast FixesNeed a quick fix or step-by-step solution? Browse our fast answers on HowTo.KandZ.me:
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!