0
# Reducer Composition
1
2
Utilities for combining multiple reducer functions into a single root reducer. Essential for organizing state management in larger applications with multiple state slices that need to be managed independently but combined into a single state tree.
3
4
## Capabilities
5
6
### Combine Reducers
7
8
Turns an object whose values are different reducer functions into a single reducer function.
9
10
```typescript { .api }
11
/**
12
* Combines multiple reducer functions into a single reducer function
13
* @param reducers - Object whose values correspond to different reducer functions
14
* @returns A reducer function that invokes every reducer inside the passed object
15
*/
16
function combineReducers<M>(
17
reducers: M
18
): M[keyof M] extends Reducer<any, any, any> | undefined
19
? Reducer<
20
StateFromReducersMapObject<M>,
21
ActionFromReducersMapObject<M>,
22
Partial<PreloadedStateShapeFromReducersMapObject<M>>
23
>
24
: never;
25
26
function combineReducers(reducers: {
27
[key: string]: Reducer<any, any, any>
28
}): Reducer<any, any, any>;
29
```
30
31
**Usage Examples:**
32
33
```typescript
34
import { combineReducers } from "redux";
35
36
// Individual reducers
37
const counterReducer = (state = 0, action) => {
38
switch (action.type) {
39
case "INCREMENT":
40
return state + 1;
41
case "DECREMENT":
42
return state - 1;
43
default:
44
return state;
45
}
46
};
47
48
const todosReducer = (state = [], action) => {
49
switch (action.type) {
50
case "ADD_TODO":
51
return [...state, action.payload];
52
case "REMOVE_TODO":
53
return state.filter(todo => todo.id !== action.payload.id);
54
default:
55
return state;
56
}
57
};
58
59
// Combine into root reducer
60
const rootReducer = combineReducers({
61
counter: counterReducer,
62
todos: todosReducer,
63
user: userReducer
64
});
65
66
// Resulting state shape will be:
67
// {
68
// counter: number,
69
// todos: Todo[],
70
// user: User
71
// }
72
```
73
74
### Reducer Function Type
75
76
The fundamental reducer function type that combines reducers must implement.
77
78
```typescript { .api }
79
/**
80
* A reducer is a function that accepts an accumulation and a value and returns a new accumulation
81
* @template S - The type of state consumed and produced by this reducer
82
* @template A - The type of actions the reducer can potentially respond to
83
* @template PreloadedState - The type of state consumed by this reducer the first time it's called
84
*/
85
type Reducer<S = any, A extends Action = UnknownAction, PreloadedState = S> = (
86
state: S | PreloadedState | undefined,
87
action: A
88
) => S;
89
```
90
91
**Usage Examples:**
92
93
```typescript
94
// Basic reducer implementation
95
const countReducer: Reducer<number> = (state = 0, action) => {
96
switch (action.type) {
97
case "INCREMENT":
98
return state + 1;
99
default:
100
return state;
101
}
102
};
103
104
// Typed reducer with specific actions
105
interface CounterAction {
106
type: "INCREMENT" | "DECREMENT" | "RESET";
107
payload?: number;
108
}
109
110
const typedCounterReducer: Reducer<number, CounterAction> = (state = 0, action) => {
111
switch (action.type) {
112
case "INCREMENT":
113
return state + (action.payload || 1);
114
case "DECREMENT":
115
return state - (action.payload || 1);
116
case "RESET":
117
return 0;
118
default:
119
return state;
120
}
121
};
122
123
// Reducer with different preloaded state type
124
interface AppState {
125
count: number;
126
name: string;
127
}
128
129
interface PreloadedAppState {
130
count?: number;
131
name?: string;
132
}
133
134
const appReducer: Reducer<AppState, UnknownAction, PreloadedAppState> = (
135
state = { count: 0, name: "" },
136
action
137
) => {
138
// Implementation
139
return state;
140
};
141
```
142
143
### Reducer Map Object
144
145
Object type whose values correspond to different reducer functions.
146
147
```typescript { .api }
148
/**
149
* Object whose values correspond to different reducer functions
150
* @template S - The combined state of the reducers
151
* @template A - The type of actions the reducers can potentially respond to
152
* @template PreloadedState - The combined preloaded state of the reducers
153
*/
154
type ReducersMapObject<S = any, A extends Action = UnknownAction, PreloadedState = S> =
155
keyof PreloadedState extends keyof S
156
? {
157
[K in keyof S]: Reducer<
158
S[K],
159
A,
160
K extends keyof PreloadedState ? PreloadedState[K] : never
161
>
162
}
163
: never;
164
```
165
166
### State Inference Types
167
168
Type utilities for inferring state shapes from reducer maps.
169
170
```typescript { .api }
171
/**
172
* Infer a combined state shape from a ReducersMapObject
173
* @template M - Object map of reducers as provided to combineReducers(map: M)
174
*/
175
type StateFromReducersMapObject<M> = M[keyof M] extends
176
| Reducer<any, any, any>
177
| undefined
178
? {
179
[P in keyof M]: M[P] extends Reducer<infer S, any, any> ? S : never
180
}
181
: never;
182
183
/**
184
* Infer reducer union type from a ReducersMapObject
185
* @template M - Object map of reducers as provided to combineReducers(map: M)
186
*/
187
type ReducerFromReducersMapObject<M> = M[keyof M] extends
188
| Reducer<any, any, any>
189
| undefined
190
? M[keyof M]
191
: never;
192
193
/**
194
* Infer action type from a reducer function
195
* @template R - Type of reducer
196
*/
197
type ActionFromReducer<R> = R extends Reducer<any, infer A, any> ? A : never;
198
199
/**
200
* Infer action union type from a ReducersMapObject
201
* @template M - Object map of reducers as provided to combineReducers(map: M)
202
*/
203
type ActionFromReducersMapObject<M> = ActionFromReducer<
204
ReducerFromReducersMapObject<M>
205
>;
206
207
/**
208
* Infer a combined preloaded state shape from a ReducersMapObject
209
* @template M - Object map of reducers as provided to combineReducers(map: M)
210
*/
211
type PreloadedStateShapeFromReducersMapObject<M> = M[keyof M] extends
212
| Reducer<any, any, any>
213
| undefined
214
? {
215
[P in keyof M]: M[P] extends (
216
inputState: infer InputState,
217
action: UnknownAction
218
) => any
219
? InputState
220
: never
221
}
222
: never;
223
```
224
225
**Usage Examples:**
226
227
```typescript
228
// Infer state type from reducer map
229
const reducerMap = {
230
counter: counterReducer,
231
todos: todosReducer,
232
user: userReducer
233
};
234
235
// StateFromReducersMapObject<typeof reducerMap> will be:
236
// {
237
// counter: number;
238
// todos: Todo[];
239
// user: User;
240
// }
241
type AppState = StateFromReducersMapObject<typeof reducerMap>;
242
243
// Use inferred types
244
const selectCounter = (state: AppState) => state.counter;
245
const selectTodos = (state: AppState) => state.todos;
246
```
247
248
## Advanced Patterns
249
250
### Nested Reducer Composition
251
252
```typescript
253
// Nested reducer composition
254
const featuresReducer = combineReducers({
255
featureA: featureAReducer,
256
featureB: featureBReducer
257
});
258
259
const rootReducer = combineReducers({
260
auth: authReducer,
261
features: featuresReducer,
262
ui: uiReducer
263
});
264
265
// Resulting state shape:
266
// {
267
// auth: AuthState,
268
// features: {
269
// featureA: FeatureAState,
270
// featureB: FeatureBState
271
// },
272
// ui: UIState
273
// }
274
```
275
276
### Conditional Reducer Composition
277
278
```typescript
279
// Dynamic reducer composition based on environment
280
const createRootReducer = (environment: string) => {
281
const baseReducers = {
282
core: coreReducer,
283
data: dataReducer
284
};
285
286
if (environment === "development") {
287
return combineReducers({
288
...baseReducers,
289
debug: debugReducer
290
});
291
}
292
293
return combineReducers(baseReducers);
294
};
295
```
296
297
### Reducer Validation
298
299
The `combineReducers` function performs several validation checks:
300
301
- Each slice reducer must return their initial state when called with `undefined` state
302
- Reducers must return the previous state for unrecognized actions
303
- Reducers must not return `undefined` for any action
304
- The state object passed to reducers should only contain keys corresponding to the reducer map
305
306
**Error Examples:**
307
308
```typescript
309
// This will throw an error - reducer returns undefined
310
const badReducer = (state, action) => {
311
if (action.type === "RESET") {
312
return undefined; // ❌ Never return undefined
313
}
314
return state;
315
};
316
317
// This will cause warnings - unexpected state keys
318
const store = createStore(combineReducers({
319
counter: counterReducer
320
}), {
321
counter: 0,
322
unexpected: "value" // ⚠️ Will warn about unexpected key
323
});
324
```