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

StatusMeaning
isPendingNo data yet, first fetch in progress
isLoadingisPending && isFetching (alias)
isFetchingAny fetch in progress (including background)
isSuccessData available
isErrorQuery failed
isStaleData older than staleTime