NotifyManager
Manages batching and scheduling of notifications in TanStack Query. This utility ensures that multiple state updates are efficiently batched together and scheduled for execution, improving performance by reducing unnecessary re-renders.
import { notifyManager } from '@tanstack/query-core'
Instance
A singleton instance is exported as notifyManager:
export const notifyManager = createNotifyManager()
Methods
batch
Executes a callback function within a batch context, deferring all notifications until the batch completes.
batch<T>(callback: () => T): T
The function to execute within the batch context. All notifications triggered during this function’s execution will be queued and flushed together when the batch completes.
Returns:
The return value of the callback function.
Usage:
import { notifyManager } from '@tanstack/query-core'
// Batch multiple state updates
notifyManager.batch(() => {
// These updates will be batched together
queryClient.setQueryData(['user', 1], user1)
queryClient.setQueryData(['user', 2], user2)
queryClient.setQueryData(['user', 3], user3)
})
// All listeners are notified once after the batch completes
Behavior:
- Increments a transaction counter before executing the callback
- Queues all scheduled notifications during callback execution
- Decrements the transaction counter after the callback completes
- Flushes all queued notifications when the transaction counter reaches zero
- Supports nested batches (transactions stack)
- The callback’s return value is preserved and returned
Advanced example with nested batches:
const result = notifyManager.batch(() => {
updateQuery1()
// Nested batch
const nestedResult = notifyManager.batch(() => {
updateQuery2()
updateQuery3()
return 'nested'
})
updateQuery4()
return nestedResult
})
// All 4 updates are flushed together after the outer batch completes
console.log(result) // 'nested'
schedule
Schedules a callback to be executed, either immediately or after the current batch completes.
schedule(callback: () => void): void
The callback function to schedule for execution.
Usage:
import { notifyManager } from '@tanstack/query-core'
// Schedule a notification
notifyManager.schedule(() => {
console.log('This will be executed asynchronously')
})
Behavior:
- If currently in a batch (transaction count > 0), adds the callback to the queue
- If not in a batch, schedules the callback to run asynchronously using the scheduler function
- By default, uses
setTimeout with 0 delay for scheduling
- Wraps the callback with the configured notify function
Example with batching:
notifyManager.batch(() => {
// These are queued
notifyManager.schedule(() => console.log('1'))
notifyManager.schedule(() => console.log('2'))
notifyManager.schedule(() => console.log('3'))
})
// All three callbacks execute together after the batch
batchCalls
Wraps a callback function so that all calls to it are automatically batched.
batchCalls<T extends Array<unknown>>(
callback: (...args: T) => void
): (...args: T) => void
callback
(...args: T) => void
required
The callback function to wrap. The wrapped function will have the same signature but all invocations will be batched.
Returns:
A new function with the same signature that schedules the original callback.
Usage:
import { notifyManager } from '@tanstack/query-core'
// Create a batched version of a function
const updateUI = notifyManager.batchCalls((data: string) => {
console.log('Updating UI with:', data)
renderComponent(data)
})
// Multiple calls are automatically batched
updateUI('data1')
updateUI('data2')
updateUI('data3')
// All three calls are scheduled together
Behavior:
- Returns a wrapped function that schedules the original callback instead of calling it directly
- Each call to the wrapped function is scheduled individually but may be batched with others
- Preserves the original function’s arguments
- Useful for ensuring callbacks from external sources are batched
Advanced example:
import { notifyManager } from '@tanstack/query-core'
// Wrap a state update function
const observer = {
notify: notifyManager.batchCalls((state: QueryState) => {
// This will be batched with other notifications
listeners.forEach((listener) => listener(state))
}),
}
// These calls are automatically batched
observer.notify(state1)
observer.notify(state2)
observer.notify(state3)
Advanced Configuration
The NotifyManager also provides methods for advanced configuration (typically used by framework integrations):
setNotifyFunction
Sets a custom function to wrap all notifications.
notifyManager.setNotifyFunction((callback: () => void) => {
// Custom notification logic
callback()
})
Use cases:
- Wrapping with
React.act() in tests
- Custom logging or debugging
- Integration with framework-specific batching mechanisms
setBatchNotifyFunction
Sets a custom function to batch multiple notifications together.
notifyManager.setBatchNotifyFunction((callback: () => void) => {
// Custom batch logic
callback()
})
Use cases:
- Integration with React’s
unstable_batchedUpdates
- Custom batching strategies for different frameworks
setScheduler
Sets a custom scheduler function for async execution.
notifyManager.setScheduler((callback: () => void) => {
// Custom scheduling logic
setTimeout(callback, 0)
})
Use cases:
- Custom timing strategies
- Integration with requestAnimationFrame
- Priority-based scheduling
Complete Example
import { notifyManager, QueryClient } from '@tanstack/query-core'
const queryClient = new QueryClient()
// Example 1: Batching multiple updates
notifyManager.batch(() => {
// All these updates are batched together
queryClient.setQueryData(['user', 1], { name: 'Alice' })
queryClient.setQueryData(['user', 2], { name: 'Bob' })
queryClient.setQueryData(['user', 3], { name: 'Charlie' })
})
// Listeners are notified once with all changes
// Example 2: Using batchCalls
const handleStateChange = notifyManager.batchCalls((state) => {
console.log('State changed:', state)
updateUI(state)
})
// These calls are automatically batched
handleStateChange({ status: 'loading' })
handleStateChange({ status: 'success', data: result })
// Example 3: Custom configuration for testing
if (process.env.NODE_ENV === 'test') {
notifyManager.setNotifyFunction((callback) => {
// Wrap with React.act in tests
act(() => {
callback()
})
})
}
// Example 4: Manual scheduling
notifyManager.schedule(() => {
console.log('This runs asynchronously')
})
Integration with TanStack Query
The NotifyManager is used internally throughout TanStack Query to batch state updates:
// In QueryObserver
class QueryObserver {
notify() {
// Notifications are automatically batched
notifyManager.schedule(() => {
this.listeners.forEach((listener) => listener(this.getCurrentResult()))
})
}
}
// In QueryClient
class QueryClient {
setQueryData(queryKey, updater) {
// Multiple setQueryData calls can be batched
return notifyManager.batch(() => {
const query = this.getQuery(queryKey)
const prevData = query.state.data
const newData = functionalUpdate(updater, prevData)
query.setState({ data: newData })
return newData
})
}
}
- Reduced re-renders: Multiple state changes are batched into a single notification cycle
- Improved responsiveness: Notifications are scheduled asynchronously, keeping the UI responsive
- Framework integration: Works with React’s batching, Vue’s reactive system, etc.
- Predictable timing: Nested batches are flattened and executed together
Type Definitions
type NotifyCallback = () => void
type NotifyFunction = (callback: () => void) => void
type BatchNotifyFunction = (callback: () => void) => void
type BatchCallsCallback<T extends Array<unknown>> = (...args: T) => void
type ScheduleFunction = (callback: () => void) => void
interface NotifyManager {
batch: <T>(callback: () => T) => T
batchCalls: <T extends Array<unknown>>(
callback: BatchCallsCallback<T>
) => BatchCallsCallback<T>
schedule: (callback: NotifyCallback) => void
setNotifyFunction: (fn: NotifyFunction) => void
setBatchNotifyFunction: (fn: BatchNotifyFunction) => void
setScheduler: (fn: ScheduleFunction) => void
}
See Also