0
# Middleware
1
2
Extensible middleware system for adding DevTools integration, persistence, Redux-style actions, and custom behaviors to Zustand stores.
3
4
## Capabilities
5
6
### DevTools Middleware
7
8
Integration with Redux DevTools Extension for debugging and development.
9
10
```typescript { .api }
11
/**
12
* DevTools middleware for debugging with Redux DevTools Extension
13
* @param initializer - State creator function
14
* @param devtoolsOptions - DevTools configuration options
15
* @returns Enhanced state creator with DevTools integration
16
*/
17
function devtools<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], U = T>(
18
initializer: StateCreator<T, [...Mps, ['zustand/devtools', never]], Mcs, U>,
19
devtoolsOptions?: DevtoolsOptions
20
): StateCreator<T, Mps, [['zustand/devtools', never], ...Mcs]>;
21
22
interface DevtoolsOptions {
23
/** Name for the store in DevTools */
24
name?: string;
25
/** Enable/disable DevTools integration */
26
enabled?: boolean;
27
/** Default action type for unnamed setState calls */
28
anonymousActionType?: string;
29
/** Store identifier for multi-store tracking */
30
store?: string;
31
}
32
33
type NamedSet<T> = (
34
partial: T | Partial<T> | ((state: T) => T | Partial<T>),
35
replace?: boolean | undefined,
36
actionType?: string | undefined,
37
actionName?: string | undefined
38
) => void;
39
```
40
41
**Usage Examples:**
42
43
```typescript
44
import { create } from "zustand";
45
import { devtools } from "zustand/middleware";
46
47
// Basic DevTools integration
48
const useStore = create()(
49
devtools(
50
(set) => ({
51
count: 0,
52
increment: () => set((state) => ({ count: state.count + 1 }), false, "increment"),
53
}),
54
{ name: "counter-store" }
55
)
56
);
57
58
// Multiple stores with DevTools
59
const useUserStore = create()(
60
devtools(
61
(set) => ({
62
users: [],
63
addUser: (user) => set((state) => ({ users: [...state.users, user] }), false, "addUser"),
64
}),
65
{ name: "user-store", store: "users" }
66
)
67
);
68
69
// Conditional DevTools (development only)
70
const useAppStore = create()(
71
devtools(
72
(set) => ({
73
theme: "light",
74
toggleTheme: () => set((state) => ({ theme: state.theme === "light" ? "dark" : "light" })),
75
}),
76
{ enabled: process.env.NODE_ENV === "development" }
77
)
78
);
79
```
80
81
### Persist Middleware
82
83
State persistence across page reloads and application restarts with configurable storage backends.
84
85
```typescript { .api }
86
/**
87
* Persistence middleware for storing state in various storage backends
88
* @param initializer - State creator function
89
* @param options - Persistence configuration options
90
* @returns Enhanced state creator with persistence
91
*/
92
function persist<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], U = T>(
93
initializer: StateCreator<T, [...Mps, ['zustand/persist', unknown]], Mcs>,
94
options: PersistOptions<T, U>
95
): StateCreator<T, Mps, [['zustand/persist', U], ...Mcs]>;
96
97
interface PersistOptions<S, PersistedState = S, PersistReturn = unknown> {
98
/** Storage key name */
99
name: string;
100
/** Storage implementation (defaults to localStorage) */
101
storage?: PersistStorage<PersistedState, PersistReturn>;
102
/** Function to select which parts of state to persist */
103
partialize?: (state: S) => PersistedState;
104
/** Version number for migrations */
105
version?: number;
106
/** Migration function for version changes */
107
migrate?: (persistedState: unknown, version: number) => PersistedState | Promise<PersistedState>;
108
/** Custom merge function for rehydration */
109
merge?: (persistedState: unknown, currentState: S) => S;
110
/** Skip automatic hydration on store creation */
111
skipHydration?: boolean;
112
/** Callback when rehydration starts */
113
onRehydrateStorage?: (state: S) => ((state?: S, error?: unknown) => void) | void;
114
}
115
116
/**
117
* Creates JSON storage adapter for various storage backends
118
* @param getStorage - Function returning storage implementation
119
* @param options - JSON storage options
120
* @returns PersistStorage instance
121
*/
122
function createJSONStorage<S, R = unknown>(
123
getStorage: () => StateStorage<R>,
124
options?: JsonStorageOptions
125
): PersistStorage<S, unknown> | undefined;
126
127
interface JsonStorageOptions {
128
/**
129
* Function to transform values during JSON parsing (deserialization)
130
* Called for each key-value pair when retrieving state from storage
131
*/
132
reviver?: (key: string, value: unknown) => unknown;
133
134
/**
135
* Function to transform values during JSON serialization
136
* Called for each key-value pair when saving state to storage
137
*/
138
replacer?: (key: string, value: unknown) => unknown;
139
}
140
141
interface StateStorage<R = unknown> {
142
getItem: (name: string) => string | null | Promise<string | null>;
143
setItem: (name: string, value: string) => R;
144
removeItem: (name: string) => R;
145
}
146
```
147
148
**Usage Examples:**
149
150
```typescript
151
import { create } from "zustand";
152
import { persist, createJSONStorage } from "zustand/middleware";
153
154
// Basic localStorage persistence
155
const useCounterStore = create()(
156
persist(
157
(set) => ({
158
count: 0,
159
increment: () => set((state) => ({ count: state.count + 1 })),
160
}),
161
{ name: "counter-storage" }
162
)
163
);
164
165
// Partial state persistence
166
const useAppStore = create()(
167
persist(
168
(set) => ({
169
user: null,
170
preferences: { theme: "light", lang: "en" },
171
tempData: "not-persisted",
172
login: (user) => set({ user }),
173
updatePreferences: (prefs) => set((state) => ({
174
preferences: { ...state.preferences, ...prefs }
175
})),
176
}),
177
{
178
name: "app-storage",
179
partialize: (state) => ({ user: state.user, preferences: state.preferences }),
180
}
181
)
182
);
183
184
// Custom storage backend (sessionStorage)
185
const useSessionStore = create()(
186
persist(
187
(set) => ({
188
sessionData: {},
189
updateSession: (data) => set({ sessionData: data }),
190
}),
191
{
192
name: "session-storage",
193
storage: createJSONStorage(() => sessionStorage),
194
}
195
)
196
);
197
198
// With version migration
199
const useVersionedStore = create()(
200
persist(
201
(set) => ({
202
settings: { version: 2 },
203
updateSettings: (settings) => set({ settings }),
204
}),
205
{
206
name: "versioned-storage",
207
version: 2,
208
migrate: (persistedState, version) => {
209
if (version === 1) {
210
// Migrate from version 1 to 2
211
return { settings: { ...persistedState, version: 2 } };
212
}
213
return persistedState;
214
},
215
}
216
)
217
);
218
```
219
220
### Subscribe with Selector Middleware
221
222
Granular subscriptions to specific state slices with custom equality functions.
223
224
```typescript { .api }
225
/**
226
* Enhances store with selector-based subscriptions
227
* @param initializer - State creator function
228
* @returns Enhanced state creator with selective subscriptions
229
*/
230
function subscribeWithSelector<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(
231
initializer: StateCreator<T, [...Mps, ['zustand/subscribeWithSelector', never]], Mcs>
232
): StateCreator<T, Mps, [['zustand/subscribeWithSelector', never], ...Mcs]>;
233
```
234
235
**Usage Examples:**
236
237
```typescript
238
import { create } from "zustand";
239
import { subscribeWithSelector } from "zustand/middleware";
240
241
const useStore = create()(
242
subscribeWithSelector((set) => ({
243
count: 0,
244
user: { name: "John", age: 30 },
245
increment: () => set((state) => ({ count: state.count + 1 })),
246
updateUser: (updates) => set((state) => ({
247
user: { ...state.user, ...updates }
248
})),
249
}))
250
);
251
252
// Subscribe to specific state slice
253
const unsubscribe = useStore.subscribe(
254
(state) => state.count,
255
(count, prevCount) => {
256
console.log("Count changed:", prevCount, "->", count);
257
}
258
);
259
260
// Subscribe with custom equality function
261
const unsubscribeUser = useStore.subscribe(
262
(state) => state.user,
263
(user, prevUser) => {
264
console.log("User changed:", prevUser, "->", user);
265
},
266
{
267
equalityFn: (prev, curr) => prev.name === curr.name && prev.age === curr.age,
268
fireImmediately: true,
269
}
270
);
271
272
// Subscribe to derived state
273
const unsubscribeAdult = useStore.subscribe(
274
(state) => state.user.age >= 18,
275
(isAdult) => {
276
console.log("Adult status:", isAdult);
277
}
278
);
279
```
280
281
### Redux Middleware
282
283
Redux-style action/reducer pattern for familiar state management.
284
285
```typescript { .api }
286
/**
287
* Redux-style middleware with action dispatch
288
* @param reducer - Reducer function handling state transitions
289
* @param initialState - Initial state value
290
* @returns State creator with dispatch capability
291
*/
292
function redux<T, A>(
293
reducer: (state: T, action: A) => T,
294
initialState: T
295
): StateCreator<T & { dispatch: (action: A) => A }, [], [], T & { dispatch: (action: A) => A }>;
296
```
297
298
**Usage Examples:**
299
300
```typescript
301
import { create } from "zustand";
302
import { redux } from "zustand/middleware";
303
304
type CounterState = { count: number };
305
type CounterAction =
306
| { type: "INCREMENT" }
307
| { type: "DECREMENT" }
308
| { type: "SET"; payload: number };
309
310
const counterReducer = (state: CounterState, action: CounterAction): CounterState => {
311
switch (action.type) {
312
case "INCREMENT":
313
return { count: state.count + 1 };
314
case "DECREMENT":
315
return { count: state.count - 1 };
316
case "SET":
317
return { count: action.payload };
318
default:
319
return state;
320
}
321
};
322
323
const useCounterStore = create()(
324
redux(counterReducer, { count: 0 })
325
);
326
327
// Usage
328
function Counter() {
329
const { count, dispatch } = useCounterStore();
330
331
return (
332
<div>
333
<p>Count: {count}</p>
334
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
335
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
336
<button onClick={() => dispatch({ type: "SET", payload: 0 })}>Reset</button>
337
</div>
338
);
339
}
340
```
341
342
### Combine Middleware
343
344
Utility for combining initial state with state creator functions.
345
346
```typescript { .api }
347
/**
348
* Combines initial state with state creator for cleaner type inference
349
* @param initialState - Initial state object
350
* @param create - State creator function for actions
351
* @returns Combined state creator
352
*/
353
function combine<T extends object, U extends object, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(
354
initialState: T,
355
create: StateCreator<T, Mps, Mcs, U>
356
): StateCreator<Write<T, U>, Mps, Mcs>;
357
```
358
359
**Usage Examples:**
360
361
```typescript
362
import { create } from "zustand";
363
import { combine } from "zustand/middleware";
364
365
// Without combine - verbose typing
366
interface BearState {
367
bears: number;
368
increase: (by: number) => void;
369
}
370
371
const useBearStore1 = create<BearState>((set) => ({
372
bears: 0,
373
increase: (by) => set((state) => ({ bears: state.bears + by })),
374
}));
375
376
// With combine - automatic type inference
377
const useBearStore2 = create()(
378
combine(
379
{ bears: 0 }, // initial state
380
(set) => ({ // actions
381
increase: (by: number) => set((state) => ({ bears: state.bears + by })),
382
})
383
)
384
);
385
386
// Complex example with nested state
387
const useAppStore = create()(
388
combine(
389
{
390
user: null as User | null,
391
settings: { theme: "light", notifications: true },
392
isLoading: false,
393
},
394
(set, get) => ({
395
login: async (credentials: LoginCredentials) => {
396
set({ isLoading: true });
397
const user = await api.login(credentials);
398
set({ user, isLoading: false });
399
},
400
logout: () => set({ user: null }),
401
updateSettings: (newSettings: Partial<Settings>) =>
402
set((state) => ({
403
settings: { ...state.settings, ...newSettings },
404
})),
405
})
406
)
407
);
408
```
409
410
### Immer Middleware
411
412
Immutable state updates using Immer's draft mechanism for mutative syntax.
413
414
```typescript { .api }
415
/**
416
* Immer middleware for mutative state updates
417
* @param initializer - State creator function with Immer support
418
* @returns Enhanced state creator with draft-based mutations
419
*/
420
function immer<T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(
421
initializer: StateCreator<T, [...Mps, ['zustand/immer', never]], Mcs>
422
): StateCreator<T, Mps, [['zustand/immer', never], ...Mcs]>;
423
```
424
425
**Usage Examples:**
426
427
```typescript
428
import { create } from "zustand";
429
import { immer } from "zustand/middleware/immer";
430
431
const useStore = create()(
432
immer((set) => ({
433
todos: [] as Todo[],
434
user: { name: "", preferences: { theme: "light" } },
435
436
addTodo: (text: string) =>
437
set((state) => {
438
state.todos.push({ id: Date.now(), text, completed: false });
439
}),
440
441
toggleTodo: (id: number) =>
442
set((state) => {
443
const todo = state.todos.find((t) => t.id === id);
444
if (todo) {
445
todo.completed = !todo.completed;
446
}
447
}),
448
449
updateUserPreference: (key: string, value: any) =>
450
set((state) => {
451
state.user.preferences[key] = value;
452
}),
453
454
clearCompleted: () =>
455
set((state) => {
456
state.todos = state.todos.filter((todo) => !todo.completed);
457
}),
458
}))
459
);
460
```
461
462
### Middleware Composition
463
464
Multiple middleware can be composed together, with inner middleware executing first.
465
466
```typescript
467
import { create } from "zustand";
468
import { devtools, persist, subscribeWithSelector } from "zustand/middleware";
469
import { immer } from "zustand/middleware/immer";
470
471
const useStore = create()(
472
devtools(
473
subscribeWithSelector(
474
persist(
475
immer((set) => ({
476
count: 0,
477
history: [] as number[],
478
increment: () =>
479
set((state) => {
480
state.history.push(state.count);
481
state.count += 1;
482
}),
483
})),
484
{ name: "counter-storage" }
485
)
486
),
487
{ name: "counter-devtools" }
488
)
489
);
490
491
// Order matters: immer -> persist -> subscribeWithSelector -> devtools
492
```