Skip to main content
The useQueries hook allows you to fetch multiple queries in parallel with a dynamic number of queries. It’s useful when you need to fetch an array of queries where the length is not known at build time.

Import

import { useQueries } from '@tanstack/react-query'

Signature

function useQueries<T extends Array<any>, TCombinedResult = QueriesResults<T>>({
  queries,
  combine,
}: {
  queries: readonly [...QueriesOptions<T>]
  combine?: (result: QueriesResults<T>) => TCombinedResult
  subscribed?: boolean
}, queryClient?: QueryClient): TCombinedResult

Parameters

options
object
required
The queries options object.
queryClient
QueryClient
Override the default QueryClient.

Query Options

Each query in the queries array accepts the following options:
queryKey
QueryKey
required
The unique key for the query.
queryFn
QueryFunction<TQueryFnData, TQueryKey>
required
The function to fetch the data.
enabled
boolean
default:"true"
Set to false to disable the query.
staleTime
number | 'static'
default:"0"
Time in milliseconds after data is considered stale.
gcTime
number
default:"300000"
Garbage collection time in milliseconds.
retry
boolean | number | ((failureCount: number, error: TError) => boolean)
default:"3"
Retry configuration.
select
(data: TQueryData) => TData
Transform or select part of the data.
All options available to useQuery are also available for each query in the array, except placeholderData which has a slightly different signature.

Returns

UseQueryResult[]
array
By default, returns an array of query results. Each element corresponds to a query in the input array.When using the combine option, returns the result of the combine function.

Examples

Basic Usage

import { useQueries } from '@tanstack/react-query'

function TodosComponent({ todoIds }: { todoIds: number[] }) {
  const results = useQueries({
    queries: todoIds.map(id => ({
      queryKey: ['todo', id],
      queryFn: () => fetchTodo(id),
      staleTime: 5000,
    })),
  })

  return (
    <div>
      {results.map((result, index) => (
        <div key={todoIds[index]}>
          {result.isLoading && <div>Loading...</div>}
          {result.isError && <div>Error: {result.error.message}</div>}
          {result.isSuccess && <div>{result.data.title}</div>}
        </div>
      ))}
    </div>
  )
}

With Type Safety

interface Todo {
  id: number
  title: string
  completed: boolean
}

const todoIds = [1, 2, 3]

const results = useQueries({
  queries: todoIds.map(id => ({
    queryKey: ['todo', id] as const,
    queryFn: async (): Promise<Todo> => {
      const response = await fetch(`/api/todos/${id}`)
      return response.json()
    },
  })),
})

// results is typed as UseQueryResult<Todo, Error>[]

Using Combine

const combinedResult = useQueries({
  queries: todoIds.map(id => ({
    queryKey: ['todo', id],
    queryFn: () => fetchTodo(id),
  })),
  combine: (results) => {
    return {
      data: results.map(result => result.data),
      isPending: results.some(result => result.isPending),
      isError: results.some(result => result.isError),
    }
  },
})

// combinedResult.data is (Todo | undefined)[]
// combinedResult.isPending is boolean
// combinedResult.isError is boolean

Dynamic Queries with Conditional Execution

const results = useQueries({
  queries: userIds.map(userId => ({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    enabled: !!userId, // Only fetch if userId exists
  })),
})

Handling All States

const results = useQueries({
  queries: todoIds.map(id => ({
    queryKey: ['todo', id],
    queryFn: () => fetchTodo(id),
  })),
})

const isLoading = results.some(result => result.isLoading)
const isError = results.some(result => result.isError)
const errors = results.filter(result => result.isError).map(result => result.error)
const allData = results.map(result => result.data)

if (isLoading) return <div>Loading todos...</div>
if (isError) return <div>Some todos failed to load</div>

return (
  <ul>
    {allData.map((todo, index) => (
      <li key={todoIds[index]}>{todo?.title}</li>
    ))}
  </ul>
)

With Select Transform

const results = useQueries({
  queries: todoIds.map(id => ({
    queryKey: ['todo', id],
    queryFn: () => fetchTodo(id),
    select: (todo: Todo) => todo.title, // Only select the title
  })),
})

// Each result.data is now just the title string

Combining with Other Data

interface CombinedData {
  todos: Todo[]
  completedCount: number
  pendingCount: number
  hasErrors: boolean
}

const combined = useQueries({
  queries: todoIds.map(id => ({
    queryKey: ['todo', id],
    queryFn: () => fetchTodo(id),
  })),
  combine: (results): CombinedData => {
    const todos = results
      .filter(r => r.data)
      .map(r => r.data!)
    
    return {
      todos,
      completedCount: todos.filter(t => t.completed).length,
      pendingCount: todos.filter(t => !t.completed).length,
      hasErrors: results.some(r => r.isError),
    }
  },
})

// combined.todos, combined.completedCount, etc.

Notes

The queries array must be a stable reference (e.g., use useMemo if generating dynamically) or the hook will re-render infinitely.
Use useQueries when the number of queries is dynamic. If you have a fixed number of queries, use multiple useQuery calls instead.
Each query in the array is tracked independently, so you can have different loading states, errors, and data for each query.
The combine option is useful for deriving computed values from multiple queries or reducing the number of re-renders.

Build docs developers (and LLMs) love