or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions.mdindex.mdmiddleware.mdreducer-composition.mdstore-management.mdutilities.md

reducer-composition.mddocs/

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

```