Skip to content

Commit cca9779

Browse files
committed
Add autoBatchEnhancer docs
1 parent ac01d3d commit cca9779

File tree

2 files changed

+132
-1
lines changed

2 files changed

+132
-1
lines changed

docs/api/autoBatchEnhancer.mdx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
---
2+
id: autoBatchEnhancer
3+
title: autoBatchEnhancer
4+
sidebar_label: autoBatchEnhancer
5+
hide_title: true
6+
---
7+
8+
 
9+
10+
# `autoBatchEnhancer`
11+
12+
A Redux store enhancer that looks for one or more "low-priority" dispatched actions in a row, and delays notifying subscribers until either the end of the current event loop tick or when the next "normal-priority" action is dispatched.
13+
14+
## Basic Usage
15+
16+
```ts
17+
import {
18+
createSlice,
19+
configureStore,
20+
autoBatchEnhancer,
21+
prepareAutoBatched,
22+
} from '@reduxjs/toolkit'
23+
24+
interface CounterState {
25+
value: number
26+
}
27+
28+
const counterSlice = createSlice({
29+
name: 'counter',
30+
initialState: { value: 0 } as CounterState,
31+
reducers: {
32+
incrementBatched: {
33+
// Batched, low-priority
34+
reducer(state) {
35+
state.value += 1
36+
},
37+
// highlight-start
38+
// Use the `prepareAutoBatched` utility to automatically
39+
// add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs
40+
prepare: prepareAutoBatched<void>(),
41+
// highlight-end
42+
},
43+
// Not batched, normal priority
44+
decrementUnbatched(state) {
45+
state.value -= 1
46+
},
47+
},
48+
})
49+
const { incrementBatched, decrementUnbatched } = counterSlice.actions
50+
51+
const store = configureStore({
52+
reducer: counterSlice.reducer,
53+
// highlight-start
54+
enhancers: (existingEnhancers) => {
55+
// Add the autobatch enhancer to the store setup
56+
return existingEnhancers.concat(autoBatchEnhancer())
57+
},
58+
// highlight-end
59+
})
60+
```
61+
62+
## API
63+
64+
### `autoBatchEnhancer`
65+
66+
```ts title="autoBatchEnhancer signature" no-transpile
67+
export type SHOULD_AUTOBATCH = string
68+
export type autoBatchEnhancer = () => StoreEnhancer
69+
```
70+
71+
Creates a new instance of the autobatch store enhancer.
72+
73+
Any action that is tagged with `action.meta[SHOULD_AUTOBATCH] = true` will be treated as "low-priority", and the enhancer will delay notifying subscribers until either:
74+
75+
- The end of the current event loop tick happens, and a queued microtask runs the notifications
76+
- A "normal-priority" action (any action _without_ `action.meta[SHOULD_AUTOBATCH] = true`) is dispatched in the same tick
77+
78+
This method currently does not accept any options. We may consider allowing customization of the delay behavior in the future.
79+
80+
The `SHOULD_AUTOBATCH` value is meant to be opaque - it's currently a string for simplicity, but could be a `Symbol` in the future.
81+
82+
### `prepareAutoBatched`
83+
84+
```ts title="prepareAutoBatched signature" no-transpile
85+
type prepareAutoBatched = <T>() => (payload: T) => { payload: T; meta: unknown }
86+
```
87+
88+
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:
89+
90+
```ts no-transpile
91+
createSlice({
92+
name: 'todos',
93+
initialState,
94+
reducers: {
95+
todoAdded: {
96+
reducer(state, action: PayloadAction<Todo>) {
97+
state.push(action.payload)
98+
},
99+
// highlight-start
100+
prepare: prepareAutoBatched<Todo>(),
101+
// highlight-end
102+
},
103+
},
104+
})
105+
```
106+
107+
## Batching Approach and Background
108+
109+
The post [A Comparison of Redux Batching Techniques](https://blog.isquaredsoftware.com/2020/01/blogged-answers-redux-batching-techniques/) describes four different approaches for "batching Redux actions/dispatches"
110+
111+
- a higher-order reducer that accepts multiple actions nested inside one real action, and iterates over them together
112+
- an enhancer that wraps `dispatch` and debounces the notification callback
113+
- an enhancer that wraps `dispatch` to accept an array of actions
114+
- React's `unstable_batchedUpdates()`, which just combines multiple queued renders into one but doesn't affect subscriber notifications
115+
116+
This enhancer is a variation of the "debounce" approach, but with a twist.
117+
118+
Instead of _just_ debouncing _all_ subscriber notifications, it watches for any actions with a specific `action.meta[SHOULD_AUTOBATCH]: true` field attached.
119+
120+
When it sees an action with that field, it queues a microtask. 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 microtask runs at the end of the event loop tick, it finally notifies all subscribers, similar to how React batches re-renders.
121+
122+
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).
123+
124+
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.
125+
126+
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.
127+
128+
### RTK Query and Batching
129+
130+
RTK Query already marks several of its key internal action types as batchable. If you add the `autoBatchEnhancer` to the store setup, it will improve the overall UI performance, especially when rendering large lists of components that use the RTKQ query hooks.

website/sidebars.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"api/getDefaultMiddleware",
4242
"api/immutabilityMiddleware",
4343
"api/serializabilityMiddleware",
44-
"api/createListenerMiddleware"
44+
"api/createListenerMiddleware",
45+
"api/autoBatchEnhancer"
4546
]
4647
},
4748
{

0 commit comments

Comments
 (0)