You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/api/createReducer.md
+90-42Lines changed: 90 additions & 42 deletions
Original file line number
Diff line number
Diff line change
@@ -5,68 +5,116 @@ sidebar_label: createReducer
5
5
hide_title: true
6
6
---
7
7
8
-
# `createReducer`
8
+
# `createReducer()`
9
9
10
-
A utility function to create reducers that handle specific action types, similar to the example function in the ["Reducing Boilerplate" Redux docs page](https://redux.js.org/recipes/reducing-boilerplate#generating-reducers). Takes an initial state value and an object that maps action types to case reducer functions. Internally, it uses the [`immer` library](https://github.com/mweststrate/immer), so you can write code in your case reducers that mutates the existing `state` value, and it will correctly generate immutably-updated state values instead.
10
+
A utility that simplifies creating Redux reducer functions, by defining them as lookup tables of functions to handle each action type. It also allows you to drastically simplify immutable update logic, by writing "mutative" code inside your reducers.
11
11
12
-
```ts
13
-
function createReducer(
14
-
initialState:State,
15
-
actionsMap:Object<String, Function>
16
-
) {}
12
+
Redux [reducers](https://redux.js.org/basics/reducers) are often implemented using a `switch` statement, with one `case` for every handled action type.
13
+
14
+
```js
15
+
functioncounterReducer(state=0, action) {
16
+
switch (action.type) {
17
+
case'increment':
18
+
return state +action.payload
19
+
case'decrement':
20
+
return state -action.payload
21
+
default:
22
+
return state
23
+
}
24
+
}
17
25
```
18
26
19
-
Example usage:
27
+
This approach works well, but is a bit boilerplate-y and error-prone. For instance, it is easy to forget the `default` case or setting the initial state.
28
+
29
+
The `createReducer` helper streamlines the implementation of such reducers. It takes two arguments. The first one is the initial state. The second is an object mapping from action types to _case reducers_, each of which handles one specific action type.
20
30
21
31
```js
22
-
import { createReducer } from'redux-starter-kit'
32
+
constcounterReducer=createReducer(0, {
33
+
increment: (state, action) => state +action.payload,
34
+
decrement: (state, action) => state -action.payload
35
+
})
36
+
```
23
37
24
-
functionaddTodo(state, action) {
25
-
const { newTodo } =action.payload
38
+
If you created action creators using `createAction()`, you can use those directly as keys for the case reducers.
26
39
27
-
// Can safely call state.push() here
28
-
state.push({ ...newTodo, completed:false })
29
-
}
40
+
```js
41
+
constincrement=createAction('increment')
42
+
constdecrement=createAction('decrement')
43
+
44
+
constcounterReducer=createReducer(0, {
45
+
[increment]: (state, action) => state +action.payload,
46
+
[decrement]: (state, action) => state -action.payload
47
+
})
48
+
```
30
49
31
-
functiontoggleTodo(state, action) {
32
-
const { index } =action.payload
50
+
## Direct State Mutation
33
51
34
-
consttodo= state[index]
35
-
// Can directly modify the todo object
36
-
todo.completed=!todo.completed
37
-
}
52
+
Redux requires reducer functions to be pure and treat state values as immutable. While this is essential for making state updates predictable and observable, it can sometimes make the implementation of such updates awkward. Consider the following example:
53
+
54
+
```js
55
+
constaddTodo=createAction('todos/add')
56
+
consttoggleTodo=createAction('todos/toggle')
38
57
39
58
consttodosReducer=createReducer([], {
40
-
ADD_TODO: addTodo,
41
-
TOGGLE_TODO: toggleTodo
59
+
[addTodo]: (state, action) => {
60
+
consttodo=action.payload
61
+
return [...state, todo]
62
+
},
63
+
[toggleTodo]: (state, action) => {
64
+
constindex=action.payload
65
+
consttodo= state[index]
66
+
return [
67
+
...state.slice(0, index),
68
+
{ ...todo, completed:!todo.completed }
69
+
...state.slice(index +1)
70
+
]
71
+
}
42
72
})
43
73
```
44
74
45
-
This doesn't mean that you _have to_ write code in your case reducers that mutates the existing `state` value, you can still write code that updates the state immutably. You can think of `immer` as a safety net, if the code is written in a way that mutates the state directly, `immer` will make sure that such update happens immutably. On the other hand the following code is still valid:
46
-
47
-
```js
48
-
import { createReducer } from'redux-starter-kit'
75
+
The `addTodo` reducer is pretty easy to follow if you know the [ES6 spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). However, the code for `toggleTodo` is much less straightforward, especially considering that it only sets a single flag.
49
76
50
-
functionaddTodo(state, action) {
51
-
const { newTodo } =action.payload
77
+
To make things easier, `createReducer` uses [immer](https://github.com/mweststrate/immer) to let you write reducers as if they were mutating the state directly. In reality, the reducer receives a proxy state that translates all mutations into equivalent copy operations.
52
78
53
-
// Updates the state immutably without relying on immer
// This push() operation gets translated into the same
86
+
// extended-array creation as in the previous example.
87
+
state.push(todo)
88
+
},
89
+
[toggleTodo]: (state, action) => {
90
+
// The "mutating" version of this case reducer is much
91
+
// more direct than the explicitly pure one.
92
+
constindex=action.payload
93
+
consttodo= state[index]
94
+
todo.completed=!todo.completed
95
+
}
96
+
})
97
+
```
59
98
60
-
consttodo= state[index]
61
-
// Updates the todo object immutably without relying on immer
62
-
returnstate.map((todo, i) => {
63
-
if (i !== index) return todo
64
-
return { ...todo, completed:!todo.completed }
65
-
})
66
-
}
99
+
If you choose to write reducers in this style, make sure to learn about the [pitfalls mentioned in the immer docs](https://github.com/mweststrate/immer#pitfalls) . Most importantly, you need to ensure that you either mutate the `state` argument or return a new state, _but not both_. For example, the following reducer would throw an exception if a `toggleTodo` action is passed:
67
100
101
+
```js
68
102
consttodosReducer=createReducer([], {
69
-
ADD_TODO: addTodo,
70
-
TOGGLE_TODO: toggleTodo
103
+
[toggleTodo]: (state, action) => {
104
+
constindex=action.payload
105
+
consttodo= state[index]
106
+
107
+
// This case reducer both mutates the passed-in state...
108
+
todo.completed=!todo.completed
109
+
110
+
// ... and returns a new value. This will throw an
0 commit comments