Skip to main content

 

autoBatchEnhancer

A Redux store enhancer that looks for one or more "low-priority" dispatched actions in a row, and queues a callback to run subscriber notifications on a delay. It then notifies subscribers either when the queued callback runs, or when the next "normal-priority" action is dispatched, whichever is first.

Basic Usage

import {
createSlice,
configureStore,
autoBatchEnhancer,
prepareAutoBatched,
} from '@reduxjs/toolkit'

interface CounterState {
value: number
}

const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 } satisfies CounterState as CounterState,
reducers: {
incrementBatched: {
// Batched, low-priority
reducer(state) {
state.value += 1
},
// Use the `prepareAutoBatched` utility to automatically
// add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs
prepare: prepareAutoBatched<void>(),
},
// Not batched, normal priority
decrementUnbatched(state) {
state.value -= 1
},
},
})
const { incrementBatched, decrementUnbatched } = counterSlice.actions

// includes batch enhancer by default, as of RTK 2.0
const store = configureStore({
reducer: counterSlice.reducer,
})

API

autoBatchEnhancer

autoBatchEnhancer signature
export type SHOULD_AUTOBATCH = string
type AutoBatchOptions =
| { type: 'tick' }
| { type: 'timer'; timeout: number }
| { type: 'raf' }
| { type: 'callback'; queueNotification: (notify: () => void) => void }

export type autoBatchEnhancer = (options?: AutoBatchOptions) => StoreEnhancer
tip

As of RTK 2.0, the autoBatchEnhancer is included by default when calling configureStore.

This means to configure it, you should instead pass an callback that receives getDefaultEnhancers and calls it with your desired settings.

Configuring autoBatchEnhancer with getDefaultEnhancers
import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
reducer: () => 0,
enhancers: (getDefaultEnhancers) =>
getDefaultEnhancers({
autoBatch: { type: 'tick' },
}),
})

Creates a new instance of the autobatch store enhancer.

Any action that is tagged with action.meta[SHOULD_AUTOBATCH] = true will be treated as "low-priority", and a notification callback will be queued. The enhancer will delay notifying subscribers until either:

  • The queued callback runs and triggers the notifications
  • A "normal-priority" action (any action without action.meta[SHOULD_AUTOBATCH] = true) is dispatched in the same tick

autoBatchEnhancer accepts options to configure how the notification callback is queued:

  • {type: 'raf'}: queues using requestAnimationFrame (default)
  • {type: 'tick'}: queues using queueMicrotask
  • {type: 'timer', timeout: number}: queues using setTimeout
  • {type: 'callback', queueNotification: (notify: () => void) => void}: lets you provide your own callback, such as a debounced or throttled function

The default behavior is to queue the notifications using requestAnimationFrame.

The SHOULD_AUTOBATCH value is meant to be opaque - it's currently a string for simplicity, but could be a Symbol in the future.

prepareAutoBatched

prepareAutoBatched signature
type prepareAutoBatched = <T>() => (payload: T) => { payload: T; meta: unknown }

Creates a function that accepts a payload value, and returns an object with {payload, meta: {[SHOULD_AUTOBATCH]: true}}. This is meant to be used with RTK's createSlice and its "prepare callback" syntax:

createSlice({
name: 'todos',
initialState,
reducers: {
todoAdded: {
reducer(state, action: PayloadAction<Todo>) {
state.push(action.payload)
},
prepare: prepareAutoBatched<Todo>(),
},
},
})

Batching Approach and Background

The post A Comparison of Redux Batching Techniques describes four different approaches for "batching Redux actions/dispatches"

  • a higher-order reducer that accepts multiple actions nested inside one real action, and iterates over them together
  • an enhancer that wraps dispatch and debounces the notification callback
  • an enhancer that wraps dispatch to accept an array of actions
  • React's unstable_batchedUpdates(), which just combines multiple queued renders into one but doesn't affect subscriber notifications

This enhancer is a variation of the "debounce" approach, but with a twist.

Instead of just debouncing all subscriber notifications, it watches for any actions with a specific action.meta[SHOULD_AUTOBATCH]: true field attached.

When it sees an action with that field, it queues a callback. The reducer is updated immediately, but the enhancer does not notify subscribers right way. If other actions with the same field are dispatched in succession, the enhancer will continue to not notify subscribers. Then, when the queued callback runs, it finally notifies all subscribers, similar to how React batches re-renders.

The additional twist is also inspired by React's separation of updates into "low-priority" and "immediate" behavior (such as a render queued by an AJAX request vs a render queued by a user input that should be handled synchronously).

If some low-pri actions have been dispatched and a notification microtask is queued, then a normal priority action (without the field) is dispatched, the enhancer will go ahead and notify all subscribers synchronously as usual, and not notify them at the end of the tick.

This allows Redux users to selectively tag certain actions for effective batching behavior, making this purely opt-in on a per-action basis, while retaining normal notification behavior for all other actions.

RTK Query and Batching

RTK Query already marks several of its key internal action types as batchable. By adding the autoBatchEnhancer to the store setup, it improves the overall UI performance, especially when rendering large lists of components that use the RTKQ query hooks.