TanStack Query Guide
Core Hooks
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
// Basic data fetching
function UserList() {
const { data, isLoading, error, isStale } = useQuery({
queryKey: ['users'], // Cache key (array for hierarchy)
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000, // 5 min before refetch
gcTime: 10 * 60 * 1000, // 10 min before garbage collected
retry: 3, // Retry 3 times on failure
refetchOnWindowFocus: false, // Don't refetch when tab regained focus
})
if (isLoading) return <Spinner />
if (error) return <Error message={error.message} />
return data.users.map(u => <User key={u.id} user={u} />)
}
// Mutations with cache invalidation
function CreateUserButton() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (newUser) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
}),
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['users'] })
},
onError: (error) => {
console.error('Failed to create user:', error)
},
})
return (
<button onClick={() => mutation.mutate({ name: 'Alice' })}>
{mutation.isPending ? 'Creating...' : 'Create User'}
</button>
)
}
Query Key Patterns
// Hierarchical keys — invalidate at any level
['users'] // All users queries
['users', userId] // Specific user
['users', userId, 'posts'] // User's posts
['users', { filters, page }] // Users with params
// Invalidate all user-related queries
queryClient.invalidateQueries({ queryKey: ['users'] })
Status Reference
| Status | Meaning |
|---|---|
| isPending | No data yet, first fetch in progress |
| isLoading | isPending && isFetching (alias) |
| isFetching | Any fetch in progress (including background) |
| isSuccess | Data available |
| isError | Query failed |
| isStale | Data older than staleTime |