Capítulo 63 de 80

Chapter 63: useQueries

Core Idea

Beyond the dynamic-array pattern (Parallel Queries chapter), useQueries' combine option merges an array of results into one referentially-stable value — useful when a component only cares about a derived shape (e.g. { data, pending }), not each query's full individual result.

Key Concepts

  • Shape: useQueries({ queries: [...], queryClient?, combine? }) — each entry in queries is a full useQuery-shaped options object (minus the top-level queryClient, which is passed once).
  • Duplicate keys warning: the same queryKey appearing more than once in the array can cause data sharing between those entries — de-duplicate the input list and map results back to the desired shape instead.
  • placeholderData limitation: exists per-query here too, but unlike useQuery it can't carry data from a "previous" query, since the array's length/shape can differ between renders.
  • combine: reduces the whole results array into a single custom value (e.g. { data: results.map(r => r.data), pending: results.some(r => r.isPending) }) — everything not explicitly included in the combined shape is discarded. The combined result is structurally shared for reference stability.
  • combine memoization: re-runs only if its own reference changes or any individual query result changed — an inline combine (like the common example) re-creates every render, so wrap it in useCallback or hoist it to avoid that.
  • select typing limitation: an inline select inside a query object in the array can't infer its argument from that same object's queryFn (falls back to unknown) — this is a known TypeScript limitation. Fix by either annotating select's parameter explicitly, or defining the query via queryOptions() first (including when overriding select on a spread queryOptions result — re-wrap the spread in queryOptions() so the override itself gets resolved).

Code Examples

const combinedQueries = useQueries({
  queries: ids.map((id) => ({ queryKey: ['post', id], queryFn: () => fetchPost(id) })),
  combine: (results) => ({
    data: results.map((r) => r.data),
    pending: results.some((r) => r.isPending),
  }),
})
  • What it demonstrates: reducing N individual query results into one small, stable derived object via combine.

Key Takeaways

  1. Use combine whenever a component only needs a derived summary (counts, flattened data, any-pending flag) rather than each query's full result object.
  2. Hoist or useCallback-wrap combine the same way as select — an inline one loses its memoization every render.
  3. For inline select type errors inside useQueries, prefer defining queries with queryOptions() over manually annotating every select parameter.

Connects To

  • Parallel Queries: the dynamic-array use case this hook primarily serves.
  • Render Optimizations: select's memoization rules, mirrored here by combine.