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
* Rewrite docs for createReducer()
This version tries to state more clearly the motivation behind
the function and how the immer-powered state mutation works.
* Add intro statement
Copy file name to clipboardExpand all lines: docs/api/createReducer.md
+92-44Lines changed: 92 additions & 44 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
-
) {}
17
-
```
18
-
19
-
Example usage:
12
+
Redux [reducers](https://redux.js.org/basics/reducers) are often implemented using a `switch` statement, with one `case` for every handled action type.
20
13
21
14
```js
22
-
import { createReducer } from'redux-starter-kit'
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
+
}
25
+
```
23
26
24
-
functionaddTodo(state, action) {
25
-
const { newTodo } =action.payload
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.
26
28
27
-
// Can safely call state.push() here
28
-
state.push({ ...newTodo, completed:false })
29
-
}
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.
30
30
31
-
functiontoggleTodo(state, action) {
32
-
const { index } =action.payload
31
+
```js
32
+
constcounterReducer=createReducr(0, {
33
+
increment: (state, action) => state +action.payload,
34
+
decrement: (state, action) => state -action.payload
35
+
})
36
+
```
33
37
34
-
consttodo= state[index]
35
-
// Can directly modify the todo object
36
-
todo.completed=!todo.completed
37
-
}
38
+
If you created action creators using `createAction()`, you can use those directly as keys for the case reducers.
39
+
40
+
```js
41
+
constincrement=createAction('increment')
42
+
constdecrement=createAction('decrement')
38
43
39
-
consttodosReducer=createReducer([], {
40
-
ADD_TODO: addTodo,
41
-
TOGGLE_TODO: toggleTodo
44
+
constcounterReducer=createReducr(0, {
45
+
[increment]: (state, action) => state +action.payload,
46
+
[decrement]: (state, action) => state -action.payload
42
47
})
43
48
```
44
49
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:
50
+
## Direct State Mutation
51
+
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:
46
53
47
54
```js
48
-
import { createReducer } from'redux-starter-kit'
55
+
constaddTodo=createAction('todos/add')
56
+
consttoggleTodo=createAction('todos/toggle')
57
+
58
+
consttodosReducer=createReducr([], {
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
+
}
72
+
})
73
+
```
49
74
50
-
functionaddTodo(state, action) {
51
-
const { newTodo } =action.payload
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.
52
76
53
-
// Updates the state immutably without relying on immer
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.
56
78
57
-
functiontoggleTodo(state, action) {
58
-
const { index } =action.payload
79
+
```js
80
+
constaddTodo=createAction('todos/add')
81
+
consttoggleTodo=createAction('todos/toggle')
82
+
83
+
consttodosReducer=createReducr([], {
84
+
[addTodo]: (state, action) => {
85
+
// 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
68
-
consttodosReducer=createReducer([], {
69
-
ADD_TODO: addTodo,
70
-
TOGGLE_TODO: toggleTodo
101
+
```js
102
+
consttodosReducer=createReducr([], {
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