Skip to main content
The QueriesObserver allows you to observe multiple queries at once. It powers hooks like useQueries, enabling you to run multiple queries in parallel and get their combined results.

Constructor

Creates a new QueriesObserver instance.
const observer = new QueriesObserver<TCombinedResult>(
  client: QueryClient,
  queries: Array<QueryObserverOptions>,
  options?: QueriesObserverOptions<TCombinedResult>
)
client
QueryClient
required
The QueryClient instance to use.
queries
Array<QueryObserverOptions>
required
Array of query options for each query to observe.
options
QueriesObserverOptions
Options for combining results.

Example

import { QueriesObserver } from '@tanstack/query-core'

const observer = new QueriesObserver(queryClient, [
  {
    queryKey: ['todos'],
    queryFn: fetchTodos,
  },
  {
    queryKey: ['users'],
    queryFn: fetchUsers,
  },
  {
    queryKey: ['posts'],
    queryFn: fetchPosts,
  },
])

Methods

subscribe

Subscribes to all queries and receives updates when any query state changes.
subscribe(
  listener: (results: Array<QueryObserverResult>) => void
): () => void
listener
(results: Array<QueryObserverResult>) => void
required
Function called when any query result changes. Receives an array of all query results.
() => void
function
Returns an unsubscribe function.

Example

const unsubscribe = observer.subscribe((results) => {
  const [todosResult, usersResult, postsResult] = results
  
  console.log('Todos:', todosResult.data)
  console.log('Users:', usersResult.data)
  console.log('Posts:', postsResult.data)
  
  // Check if all queries are loaded
  const allLoaded = results.every(r => r.isSuccess)
  console.log('All loaded:', allLoaded)
})

// Later, unsubscribe
unsubscribe()

setQueries

Updates the queries being observed.
setQueries(
  queries: Array<QueryObserverOptions>,
  options?: QueriesObserverOptions<TCombinedResult>
): void
queries
Array<QueryObserverOptions>
required
New array of query options.
options
QueriesObserverOptions
Updated options for combining results.

Example

// Add a new query to the observer
observer.setQueries([
  { queryKey: ['todos'], queryFn: fetchTodos },
  { queryKey: ['users'], queryFn: fetchUsers },
  { queryKey: ['posts'], queryFn: fetchPosts },
  { queryKey: ['comments'], queryFn: fetchComments }, // New query
])

getCurrentResult

Returns the current results for all queries.
getCurrentResult(): Array<QueryObserverResult>
Array<QueryObserverResult>
Array
Returns an array of query results, one for each query.

Example

const results = observer.getCurrentResult()
results.forEach((result, index) => {
  console.log(`Query ${index}:`, result.status, result.data)
})

getQueries

Returns the Query instances being observed.
getQueries(): Array<Query>
Array<Query>
Array
Returns an array of Query instances.

getObservers

Returns the QueryObserver instances managing each query.
getObservers(): Array<QueryObserver>
Array<QueryObserver>
Array
Returns an array of QueryObserver instances.

getOptimisticResult

Computes an optimistic result for the given queries without subscribing.
getOptimisticResult(
  queries: Array<QueryObserverOptions>,
  combine?: (results: Array<QueryObserverResult>) => TCombinedResult
): [
  rawResult: Array<QueryObserverResult>,
  combineResult: (r?: Array<QueryObserverResult>) => TCombinedResult,
  trackResult: () => Array<QueryObserverResult>
]
queries
Array<QueryObserverOptions>
required
Array of query options.
combine
(results: Array<QueryObserverResult>) => TCombinedResult
Function to combine the results.
tuple
[Array<QueryObserverResult>, Function, Function]
Returns a tuple with:
  1. Raw results array
  2. Function to combine results
  3. Function to track results

destroy

Destroys the observer and all child observers.
destroy(): void

Example

observer.destroy()

Combining Results

You can use the combine option to transform the array of results into a single value:
const observer = new QueriesObserver(
  queryClient,
  [
    { queryKey: ['todos'], queryFn: fetchTodos },
    { queryKey: ['users'], queryFn: fetchUsers },
  ],
  {
    combine: (results) => {
      return {
        data: results.map(r => r.data),
        isPending: results.some(r => r.isPending),
        isError: results.some(r => r.isError),
      }
    },
  }
)

const unsubscribe = observer.subscribe((results) => {
  // results is still the array, but the combine function
  // is used for optimistic results and tracking
  console.log('All data:', results.map(r => r.data))
})

Usage Example

Here’s a complete example:
import { QueryClient, QueriesObserver } from '@tanstack/query-core'

const queryClient = new QueryClient()

const observer = new QueriesObserver(
  queryClient,
  [
    {
      queryKey: ['todos'],
      queryFn: async () => {
        const res = await fetch('/api/todos')
        return res.json()
      },
    },
    {
      queryKey: ['users'],
      queryFn: async () => {
        const res = await fetch('/api/users')
        return res.json()
      },
    },
  ],
  {
    combine: (results) => ({
      todos: results[0]?.data,
      users: results[1]?.data,
      isLoading: results.some(r => r.isLoading),
      isError: results.some(r => r.isError),
      errors: results.map(r => r.error).filter(Boolean),
    }),
  }
)

const unsubscribe = observer.subscribe((results) => {
  const [todosResult, usersResult] = results
  
  if (todosResult.isLoading || usersResult.isLoading) {
    console.log('Loading...')
  } else if (todosResult.isError) {
    console.error('Todos error:', todosResult.error)
  } else if (usersResult.isError) {
    console.error('Users error:', usersResult.error)
  } else {
    console.log('Todos:', todosResult.data)
    console.log('Users:', usersResult.data)
  }
})

// Update queries dynamically
setTimeout(() => {
  observer.setQueries([
    { queryKey: ['todos'], queryFn: fetchTodos },
    { queryKey: ['users'], queryFn: fetchUsers },
    { queryKey: ['posts'], queryFn: fetchPosts }, // Add new query
  ])
}, 5000)

// Cleanup
unsubscribe()
observer.destroy()

Dynamic Queries

QueriesObserver efficiently handles dynamic query lists:
// Start with 2 queries
observer.setQueries([
  { queryKey: ['user', 1], queryFn: () => fetchUser(1) },
  { queryKey: ['user', 2], queryFn: () => fetchUser(2) },
])

// Add more queries
observer.setQueries([
  { queryKey: ['user', 1], queryFn: () => fetchUser(1) },
  { queryKey: ['user', 2], queryFn: () => fetchUser(2) },
  { queryKey: ['user', 3], queryFn: () => fetchUser(3) },
  { queryKey: ['user', 4], queryFn: () => fetchUser(4) },
])

// Remove queries
observer.setQueries([
  { queryKey: ['user', 1], queryFn: () => fetchUser(1) },
])
The observer will:
  • Reuse existing QueryObserver instances for queries that haven’t changed
  • Create new QueryObserver instances for new queries
  • Destroy QueryObserver instances for removed queries
  • Only notify listeners if the results actually changed

Performance

QueriesObserver is optimized for performance:
  1. Efficient Updates: Only creates/destroys observers when queries change
  2. Selective Notifications: Only notifies listeners when results change
  3. Result Memoization: Memoizes combined results to avoid unnecessary recalculations
  4. Property Tracking: Tracks which properties are accessed to minimize re-renders

Warnings

QueriesObserver will warn you if you have duplicate queries (queries with the same queryHash). This might lead to unexpected behavior.
// This will trigger a warning
const observer = new QueriesObserver(queryClient, [
  { queryKey: ['todos'], queryFn: fetchTodos },
  { queryKey: ['todos'], queryFn: fetchTodos }, // Duplicate!
])

Build docs developers (and LLMs) love