0
# Middleware and Enhancement
1
2
Store enhancement system for adding middleware and extending store capabilities. Middleware enables async action handling, logging, routing, and other cross-cutting concerns by intercepting and potentially transforming actions before they reach reducers.
3
4
## Capabilities
5
6
### Apply Middleware
7
8
Creates a store enhancer that applies middleware to the dispatch method of the Redux store.
9
10
```typescript { .api }
11
/**
12
* Creates a store enhancer that applies middleware to the dispatch method
13
* @param middlewares - The middleware chain to be applied
14
* @returns A store enhancer applying the middleware
15
*/
16
function applyMiddleware(): StoreEnhancer;
17
18
function applyMiddleware<Ext1, S>(
19
middleware1: Middleware<Ext1, S, any>
20
): StoreEnhancer<{ dispatch: Ext1 }>;
21
22
function applyMiddleware<Ext1, Ext2, S>(
23
middleware1: Middleware<Ext1, S, any>,
24
middleware2: Middleware<Ext2, S, any>
25
): StoreEnhancer<{ dispatch: Ext1 & Ext2 }>;
26
27
function applyMiddleware<Ext1, Ext2, Ext3, S>(
28
middleware1: Middleware<Ext1, S, any>,
29
middleware2: Middleware<Ext2, S, any>,
30
middleware3: Middleware<Ext3, S, any>
31
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 }>;
32
33
function applyMiddleware<Ext1, Ext2, Ext3, Ext4, S>(
34
middleware1: Middleware<Ext1, S, any>,
35
middleware2: Middleware<Ext2, S, any>,
36
middleware3: Middleware<Ext3, S, any>,
37
middleware4: Middleware<Ext4, S, any>
38
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 }>;
39
40
function applyMiddleware<Ext1, Ext2, Ext3, Ext4, Ext5, S>(
41
middleware1: Middleware<Ext1, S, any>,
42
middleware2: Middleware<Ext2, S, any>,
43
middleware3: Middleware<Ext3, S, any>,
44
middleware4: Middleware<Ext4, S, any>,
45
middleware5: Middleware<Ext5, S, any>
46
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 & Ext5 }>;
47
48
function applyMiddleware<Ext, S = any>(
49
...middlewares: Middleware<any, S, any>[]
50
): StoreEnhancer<{ dispatch: Ext }>;
51
```
52
53
**Usage Examples:**
54
55
```typescript
56
import { applyMiddleware, legacy_createStore as createStore } from "redux";
57
import thunk from "redux-thunk";
58
import logger from "redux-logger";
59
60
// Single middleware
61
const store = createStore(rootReducer, applyMiddleware(thunk));
62
63
// Multiple middleware (order matters - applied left to right)
64
const store = createStore(
65
rootReducer,
66
applyMiddleware(thunk, logger)
67
);
68
69
// With preloaded state
70
const store = createStore(
71
rootReducer,
72
preloadedState,
73
applyMiddleware(thunk, logger)
74
);
75
76
// Conditional middleware based on environment
77
const middlewares = [thunk];
78
if (process.env.NODE_ENV === "development") {
79
middlewares.push(logger);
80
}
81
const store = createStore(rootReducer, applyMiddleware(...middlewares));
82
```
83
84
### Function Composition
85
86
Composes single-argument functions from right to left for combining store enhancers and middleware.
87
88
```typescript { .api }
89
/**
90
* Composes single-argument functions from right to left
91
* @param funcs - The functions to compose
92
* @returns A function obtained by composing the argument functions from right to left
93
*/
94
function compose(): <R>(a: R) => R;
95
96
function compose<F extends Function>(f: F): F;
97
98
function compose<A, T extends any[], R>(
99
f1: (a: A) => R,
100
f2: Func<T, A>
101
): Func<T, R>;
102
103
function compose<A, B, T extends any[], R>(
104
f1: (b: B) => R,
105
f2: (a: A) => B,
106
f3: Func<T, A>
107
): Func<T, R>;
108
109
function compose<A, B, C, T extends any[], R>(
110
f1: (c: C) => R,
111
f2: (b: B) => C,
112
f3: (a: A) => B,
113
f4: Func<T, A>
114
): Func<T, R>;
115
116
function compose<R>(
117
f1: (a: any) => R,
118
...funcs: Function[]
119
): (...args: any[]) => R;
120
121
function compose<R>(...funcs: Function[]): (...args: any[]) => R;
122
123
type Func<T extends any[], R> = (...a: T) => R;
124
```
125
126
**Usage Examples:**
127
128
```typescript
129
import { compose, applyMiddleware } from "redux";
130
131
// Compose multiple enhancers
132
const enhancer = compose(
133
applyMiddleware(thunk, logger),
134
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
135
);
136
137
const store = createStore(rootReducer, enhancer);
138
139
// Compose functions (general use)
140
const addOne = (x: number) => x + 1;
141
const multiplyByTwo = (x: number) => x * 2;
142
const addThenMultiply = compose(multiplyByTwo, addOne);
143
144
console.log(addThenMultiply(3)); // (3 + 1) * 2 = 8
145
146
// Compose multiple transformation functions
147
const trim = (str: string) => str.trim();
148
const toLowerCase = (str: string) => str.toLowerCase();
149
const removeSpaces = (str: string) => str.replace(/\s+/g, "");
150
151
const normalizeString = compose(removeSpaces, toLowerCase, trim);
152
console.log(normalizeString(" Hello World ")); // "helloworld"
153
```
154
155
### Middleware Interface
156
157
The interface that Redux middleware must implement.
158
159
```typescript { .api }
160
/**
161
* A middleware is a higher-order function that composes a dispatch function
162
* to return a new dispatch function
163
* @template DispatchExt - Extra Dispatch signature added by this middleware
164
* @template S - The type of the state supported by this middleware
165
* @template D - The type of Dispatch of the store where this middleware is installed
166
*/
167
interface Middleware<
168
_DispatchExt = {},
169
S = any,
170
D extends Dispatch = Dispatch
171
> {
172
(api: MiddlewareAPI<D, S>): (
173
next: (action: unknown) => unknown
174
) => (action: unknown) => unknown;
175
}
176
177
/**
178
* The API object passed to middleware providing access to dispatch and getState
179
* @template D - The dispatch function type
180
* @template S - The state type
181
*/
182
interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
183
dispatch: D;
184
getState(): S;
185
}
186
```
187
188
**Usage Examples:**
189
190
```typescript
191
// Logger middleware
192
const loggerMiddleware: Middleware = (store) => (next) => (action) => {
193
console.log("Dispatching:", action);
194
console.log("Previous state:", store.getState());
195
196
const result = next(action);
197
198
console.log("Next state:", store.getState());
199
return result;
200
};
201
202
// Async middleware (like redux-thunk)
203
const thunkMiddleware: Middleware = (store) => (next) => (action) => {
204
if (typeof action === "function") {
205
return action(store.dispatch, store.getState);
206
}
207
return next(action);
208
};
209
210
// Error handling middleware
211
const crashReporterMiddleware: Middleware = (store) => (next) => (action) => {
212
try {
213
return next(action);
214
} catch (err) {
215
console.error("Caught an exception!", err);
216
// Report to crash reporting service
217
throw err;
218
}
219
};
220
221
// Performance monitoring middleware
222
const performanceMiddleware: Middleware = (store) => (next) => (action) => {
223
const start = performance.now();
224
const result = next(action);
225
const end = performance.now();
226
227
console.log(`Action ${action.type} took ${end - start} milliseconds`);
228
return result;
229
};
230
```
231
232
## Advanced Middleware Patterns
233
234
### Conditional Middleware
235
236
Middleware that only processes certain types of actions:
237
238
```typescript
239
const apiMiddleware: Middleware = (store) => (next) => (action) => {
240
// Only handle actions with 'api' meta property
241
if (!action.meta || !action.meta.api) {
242
return next(action);
243
}
244
245
const { url, method, types } = action.meta.api;
246
const [requestType, successType, failureType] = types;
247
248
// Dispatch request action
249
next({ type: requestType });
250
251
// Make API call
252
return fetch(url, { method })
253
.then(response => response.json())
254
.then(data => next({ type: successType, payload: data }))
255
.catch(error => next({ type: failureType, payload: error.message }));
256
};
257
```
258
259
### Middleware with State Dependencies
260
261
Middleware that behaves differently based on current state:
262
263
```typescript
264
const authMiddleware: Middleware = (store) => (next) => (action) => {
265
const state = store.getState();
266
267
// Redirect to login if user is not authenticated
268
if (!state.auth.isAuthenticated && action.type !== "LOGIN") {
269
console.warn("User not authenticated, redirecting to login");
270
return next({ type: "REDIRECT_TO_LOGIN" });
271
}
272
273
return next(action);
274
};
275
```
276
277
### Middleware Composition
278
279
Creating middleware that combines multiple behaviors:
280
281
```typescript
282
const createCompositeMiddleware = (...middlewares: Middleware[]) => {
283
return (store: MiddlewareAPI) => (next: Dispatch) => {
284
// Compose all middleware functions
285
const chain = middlewares.map(middleware => middleware(store));
286
return compose(...chain)(next);
287
};
288
};
289
290
// Use composite middleware
291
const compositeMiddleware = createCompositeMiddleware(
292
loggerMiddleware,
293
performanceMiddleware,
294
crashReporterMiddleware
295
);
296
```
297
298
### Type-Safe Middleware
299
300
Creating fully typed middleware with TypeScript:
301
302
```typescript
303
interface AppState {
304
counter: number;
305
user: User | null;
306
}
307
308
interface AppAction {
309
type: string;
310
payload?: any;
311
}
312
313
const typedMiddleware: Middleware<{}, AppState, Dispatch<AppAction>> =
314
(store) => (next) => (action) => {
315
// Full type safety for state and actions
316
const currentState: AppState = store.getState();
317
318
if (action.type === "INCREMENT" && currentState.counter >= 100) {
319
console.warn("Counter limit reached");
320
return next({ type: "COUNTER_LIMIT_REACHED" });
321
}
322
323
return next(action);
324
};
325
```
326
327
## Store Enhancement
328
329
### Store Enhancer Type
330
331
The type definition for store enhancers.
332
333
```typescript { .api }
334
/**
335
* A store enhancer is a higher-order function that composes a store creator
336
* to return a new, enhanced store creator
337
* @template Ext - Store extension that is mixed into the Store type
338
* @template StateExt - State extension that is mixed into the state type
339
*/
340
type StoreEnhancer<Ext extends {} = {}, StateExt extends {} = {}> = <
341
NextExt extends {},
342
NextStateExt extends {}
343
>(
344
next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
345
) => StoreEnhancerStoreCreator<NextExt & Ext, NextStateExt & StateExt>;
346
347
type StoreEnhancerStoreCreator<
348
Ext extends {} = {},
349
StateExt extends {} = {}
350
> = <S, A extends Action, PreloadedState>(
351
reducer: Reducer<S, A, PreloadedState>,
352
preloadedState?: PreloadedState | undefined
353
) => Store<S, A, StateExt> & Ext;
354
```
355
356
### Custom Store Enhancers
357
358
Creating custom store enhancers for advanced functionality:
359
360
```typescript
361
// DevTools enhancer
362
const devToolsEnhancer: StoreEnhancer = (createStore) => (reducer, preloadedState) => {
363
const store = createStore(reducer, preloadedState);
364
365
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
366
return {
367
...store,
368
// Add devtools capabilities
369
};
370
}
371
372
return store;
373
};
374
375
// Persistence enhancer
376
const persistenceEnhancer: StoreEnhancer = (createStore) => (reducer, preloadedState) => {
377
// Load persisted state
378
const persistedState = localStorage.getItem("redux-state");
379
const initialState = persistedState ? JSON.parse(persistedState) : preloadedState;
380
381
const store = createStore(reducer, initialState);
382
383
// Save state on every change
384
store.subscribe(() => {
385
localStorage.setItem("redux-state", JSON.stringify(store.getState()));
386
});
387
388
return store;
389
};
390
391
// Combine enhancers
392
const rootEnhancer = compose(
393
applyMiddleware(thunk, logger),
394
devToolsEnhancer,
395
persistenceEnhancer
396
);
397
```