Skip to main content
Queries are the foundation of TanStack Query. They represent a declarative dependency on an asynchronous source of data that is tied to a unique key.

Basic Query

To subscribe to a query in your components, use the useQuery hook with at least:
  • A unique key for the query
  • A function that returns a promise
import { useQuery } from '@tanstack/react-query'

function App() {
  const { data, error, status } = useQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const response = await fetch('https://api.example.com/todos')
      return response.json()
    }
  })

  if (status === 'pending') return <div>Loading...</div>
  if (status === 'error') return <div>Error: {error.message}</div>

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

Query Results

The query result contains all information about the query state:
const {
  // Data states
  data,              // The last successfully resolved data
  error,             // The error object if the query failed
  status,            // 'pending' | 'error' | 'success'
  
  // Fetch states  
  fetchStatus,       // 'fetching' | 'paused' | 'idle'
  isFetching,        // Is the query currently fetching?
  isPending,         // Is the query in pending state?
  isLoading,         // Is it both pending AND fetching?
  
  // Derived states
  isSuccess,         // Did the query succeed?
  isError,           // Did the query fail?
  isStale,           // Is the data stale?
  isPlaceholderData, // Is the data from placeholder?
  
  // Actions
  refetch,           // Function to manually refetch
  
  // Metadata
  dataUpdatedAt,     // Timestamp of last successful fetch
  errorUpdatedAt,    // Timestamp of last error
  failureCount,      // Number of consecutive failures
  failureReason,     // The error from the last failed attempt
} = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
The status field indicates the data state (pending/error/success), while fetchStatus indicates the fetch operation state (fetching/paused/idle).

Query Options

Stale Time

The time in milliseconds after data is considered stale. Defaults to 0.
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: 5000, // Data is fresh for 5 seconds
})
Set staleTime: Infinity for data that never needs to be refetched, or use the special 'static' value for compile-time static data.

GC Time (formerly Cache Time)

The time in milliseconds that unused/inactive cache data remains in memory. Defaults to 5 minutes.
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  gcTime: 1000 * 60 * 10, // 10 minutes
})

Retry

Control how queries retry on failure:
// Retry 3 times
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  retry: 3,
})

// Retry infinitely
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  retry: true,
})

// Conditional retry
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  retry: (failureCount, error) => {
    // Don't retry on 404
    if (error.status === 404) return false
    // Retry up to 3 times
    return failureCount < 3
  },
})

Retry Delay

Customize the delay between retries:
import { useQuery } from '@tanstack/react-query'

// Default: exponential backoff (1s, 2s, 4s, ...)
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
})

// Fixed delay
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  retryDelay: 1000, // Always wait 1 second
})

Initial Data

Provide initial data to prevent loading states:
useQuery({
  queryKey: ['todo', todoId],
  queryFn: () => fetchTodo(todoId),
  initialData: () => {
    // Use data from another query as initial data
    return queryClient
      .getQueryData(['todos'])
      ?.find(todo => todo.id === todoId)
  },
})
Queries with initialData start in success status immediately.

Placeholder Data

Show temporary data while the query loads:
useQuery({
  queryKey: ['todo', todoId],
  queryFn: () => fetchTodo(todoId),
  placeholderData: previousData => previousData,
})
placeholderData is never persisted to the cache. For persisted initial data, use initialData.

Enabled Queries

Control when queries run:
function Todos({ userId }) {
  // This query will not execute until userId exists
  const { data } = useQuery({
    queryKey: ['todos', userId],
    queryFn: () => fetchTodosByUser(userId),
    enabled: !!userId,
  })
}

Refetching

function Todos() {
  const { data, refetch } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  return (
    <div>
      <button onClick={() => refetch()}>Refetch</button>
      {/* ... */}
    </div>
  )
}

Polling with Refetch Interval

Automatically refetch at regular intervals:
// Poll every 5 seconds
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  refetchInterval: 5000,
})

// Poll only when window is focused
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  refetchInterval: 5000,
  refetchIntervalInBackground: false, // Default
})

// Dynamic interval
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  refetchInterval: (query) => {
    return query.state.data?.length > 0 ? 10000 : 5000
  },
})

Select Data

Transform or select a portion of query data:
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  select: (data) => {
    // Only return completed todos
    return data.filter(todo => todo.completed)
  },
})
The select function is only called when data exists and is memoized, so it only runs when the data changes.

Dependent Queries

Queries that depend on previous queries:
function User({ userId }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  })

  const userProjectsEnabled = !!user?.id

  const { data: projects } = useQuery({
    queryKey: ['projects', user?.id],
    queryFn: () => fetchProjectsByUser(user.id),
    enabled: userProjectsEnabled,
  })
}

Parallel Queries

Multiple queries in the same component run in parallel:
function Overview() {
  // These run in parallel
  const usersQuery = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  })

  const projectsQuery = useQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
  })

  const tasksQuery = useQuery({
    queryKey: ['tasks'],
    queryFn: fetchTasks,
  })
}

Dynamic Parallel Queries with useQueries

For a variable number of queries:
import { useQueries } from '@tanstack/react-query'

function Projects({ projectIds }) {
  const projectQueries = useQueries({
    queries: projectIds.map(id => ({
      queryKey: ['project', id],
      queryFn: () => fetchProject(id),
    })),
  })

  // Check if all queries are successful
  const allSuccess = projectQueries.every(q => q.isSuccess)
}

Query Cancellation

Queries support cancellation via AbortSignal:
useQuery({
  queryKey: ['todos'],
  queryFn: async ({ signal }) => {
    const response = await fetch('/api/todos', { signal })
    return response.json()
  },
})
When a query is cancelled (e.g., component unmounts), TanStack Query automatically calls abort() on the signal.

Error Handling

useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  throwOnError: true, // Throw errors to error boundary
})

Query Meta

Attach metadata to queries:
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  meta: {
    errorMessage: 'Failed to fetch todos',
  },
})
Access meta in global callbacks:
const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      console.log(query.meta?.errorMessage)
    },
  }),
})

Build docs developers (and LLMs) love