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 >
)
The QueryClient instance to use.
queries
Array<QueryObserverOptions>
required
Array of query options for each query to observe.
Options for combining results. combine
(results: Array<QueryObserverResult>) => TCombinedResult
Function to combine the results from all queries into a single value.
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.
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.
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>
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 >
Returns an array of Query instances.
getObservers
Returns the QueryObserver instances managing each query.
getObservers (): Array < QueryObserver >
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:
Raw results array
Function to combine results
Function to track results
destroy
Destroys the observer and all child observers.
Example
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
QueriesObserver is optimized for performance:
Efficient Updates : Only creates/destroys observers when queries change
Selective Notifications : Only notifies listeners when results change
Result Memoization : Memoizes combined results to avoid unnecessary recalculations
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!
])