The useQueries hook can be used to fetch a variable number of queries:
const ids = [1, 2, 3]
const results = useQueries({
queries: ids.map((id) => ({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
staleTime: Infinity,
})),
})
// `results` is a readonly ref — unwrap it to access the array
results.valueOptions
The useQueries hook accepts an options object with a queries key whose value is an array with query option objects identical to the useQuery hook (excluding the queryClient option - because the QueryClient can be passed in on the top level).
queryClient?: QueryClient
combine?: (result: UseQueriesResults) => TCombinedResult
Having the same query key more than once in the array of query objects may cause some data to be shared between queries. To avoid this, consider de-duplicating the queries and map the results back to the desired structure.
placeholderData
The placeholderData option exists for useQueries as well, but it doesn't get information passed from previously rendered Queries like useQuery does, because the input to useQueries can be a different number of Queries on each render.
Returns
The useQueries hook returns a readonly ref whose .value is an array with all the query results. The order returned is the same as the input order.
If you want to combine data (or other Query information) from the results into a single value, you can use the combine option. The result will be structurally shared to be as referentially stable as possible.
const ids = [1, 2, 3]
const combinedQueries = useQueries({
queries: ids.map((id) => ({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
})),
combine: (results) => {
return {
data: results.map((result) => result.data),
pending: results.some((result) => result.isPending),
}
},
})In the above example, combinedQueries.value will be an object with a data and a pending property. Note that all other properties of the Query results will be lost.
The combine function will only re-run if:
the combine function itself changed referentially
any of the query results changed
Unlike React, a Vue setup() function only runs once per component instance, so an inlined combine function, as shown above, already has a stable reference across reactive updates — no extra memoization is needed.
Unlike useQuery, useQueries cannot infer the data argument of an inline select from its sibling queryFn. Because useQueries infers the type of the whole queries array at once, the select parameter of a query object written inline cannot be contextually typed from that same object's queryFn, so it falls back to unknown. This is a known TypeScript limitation.
useQueries({
queries: [
{
queryKey: ['post', 1],
queryFn: () => fetchPost(1),
// ❌ `data` is `unknown` here
select: (data) => data.title,
},
],
})There are two supported workarounds:
Annotate the select parameter explicitly:
useQueries({
queries: [
{
queryKey: ['post', 1],
queryFn: () => fetchPost(1),
// ✅ `data` is `Post`
select: (data: Post) => data.title,
},
],
})Define the query with the queryOptions helper, which resolves its types in a single object before it reaches useQueries:
const postOptions = (id: number) =>
queryOptions({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
// ✅ `data` is `Post`
select: (data) => data.title,
})
useQueries({ queries: [postOptions(1), postOptions(2)] })The same limitation applies when you spread a queryOptions result to override its select inline — the overriding select still falls back to unknown:
useQueries({
queries: [
{
...postOptions(1),
// ❌ `data` is `unknown` here
select: (data) => data.title,
},
],
})Wrap the spread in queryOptions again so the override is resolved before it reaches useQueries:
useQueries({
queries: [
queryOptions({
...postOptions(1),
// ✅ `data` is `Post`
select: (data) => data.title,
}),
],
})