@@ -130,6 +130,196 @@ const todosSlice = createSlice({
130
130
})
131
131
` ` `
132
132
133
+ ### The ` reducers ` " creator callback" notation
134
+
135
+ Alternatively , the ` reducers ` field can be a callback which receives a " create" object .
136
+
137
+ The main benefit of this is that you can create [async thunks ](./ createAsyncThunk ) as part of your slice . Types are also slightly simplified for prepared reducers .
138
+
139
+ ` ` ` ts title="Creator callback for reducers"
140
+ import { createSlice, nanoid } from '@reduxjs/toolkit'
141
+ import type { PayloadAction } from '@reduxjs/toolkit'
142
+
143
+ interface Item {
144
+ id: string
145
+ text: string
146
+ }
147
+
148
+ interface TodoState {
149
+ loading: boolean
150
+ todos: Item[]
151
+ }
152
+
153
+ const todosSlice = createSlice({
154
+ name: 'todos',
155
+ initialState: {
156
+ loading: false,
157
+ todos: [],
158
+ } as TodoState,
159
+ reducers: (create) => ({
160
+ deleteTodo: create.reducer((state, action: PayloadAction<number>) => {
161
+ state.todos.splice(action.payload, 1)
162
+ }),
163
+ addTodo: create.preparedReducer(
164
+ (text: string) => {
165
+ const id = nanoid()
166
+ return { payload: { id, text } }
167
+ },
168
+ // action type is inferred from prepare callback
169
+ (state, action) => {
170
+ state.todos.push(action.payload)
171
+ }
172
+ ),
173
+ fetchTodo: create.asyncThunk(
174
+ async (id: string, thunkApi) => {
175
+ const res = await fetch( ` myApi / todos ? id = $ {id }` )
176
+ return (await res.json()) as Item
177
+ },
178
+ {
179
+ pending: (state) => {
180
+ state.loading = true
181
+ },
182
+ rejected: (state, action) => {
183
+ state.loading = false
184
+ },
185
+ fulfilled: (state, action) => {
186
+ state.loading = false
187
+ state.todos.push(action.payload)
188
+ },
189
+ }
190
+ ),
191
+ }),
192
+ })
193
+
194
+ export const { addTodo, deleteTodo, fetchTodo } = todosSlice.actions
195
+ ` ` `
196
+
197
+ #### Create Methods
198
+
199
+ #### ` create.reducer `
200
+
201
+ A standard slice case reducer .
202
+
203
+ ** Parameters **
204
+
205
+ - ** reducer ** The slice case reducer to use .
206
+
207
+ ` ` ` ts no-transpile
208
+ create.reducer((state, action: PayloadAction<Todo>) => {
209
+ state.todos.push(action.payload)
210
+ })
211
+ ` ` `
212
+
213
+ #### ` create.preparedReducer `
214
+
215
+ A [prepared ](#customizing - generated - action - creators ) reducer , to customize the action creator .
216
+
217
+ ** Parameters **
218
+
219
+ - ** prepareAction ** The [` prepare callback ` ](./ createAction #using - prepare - callbacks - to - customize - action - contents ).
220
+ - ** reducer ** The slice case reducer to use .
221
+
222
+ The action passed to the case reducer will be inferred from the prepare callback ' s return.
223
+
224
+ ` ` ` ts no-transpile
225
+ create.preparedReducer(
226
+ (text: string) => {
227
+ const id = nanoid()
228
+ return { payload: { id, text } }
229
+ },
230
+ (state, action) => {
231
+ state.todos.push(action.payload)
232
+ }
233
+ )
234
+ ` ` `
235
+
236
+ #### ` create.asyncThunk `
237
+
238
+ Creates an async thunk instead of an action creator .
239
+
240
+ ** Parameters **
241
+
242
+ - ** payloadCreator ** The thunk [payload creator ](./ createAsyncThunk #payloadcreator ).
243
+ - ** config ** The configuration object . (optional )
244
+
245
+ The configuration object can contain case reducers for each of the [lifecycle actions ](./ createAsyncThunk #promise - lifecycle - actions ) (` pending ` , ` fulfilled ` , and ` rejected ` ).
246
+
247
+ Each case reducer will be attached to the slice ' s `caseReducers` object, e.g. `slice.caseReducers.fetchTodo.fulfilled`.
248
+
249
+ The configuration object can also contain [` options ` ](./ createAsyncThunk #options ).
250
+
251
+ ` ` ` ts no-transpile
252
+ create.asyncThunk(
253
+ async (id: string, thunkApi) => {
254
+ const res = await fetch( ` myApi / todos ? id = $ {id }` )
255
+ return (await res.json()) as Item
256
+ },
257
+ {
258
+ pending: (state) => {
259
+ state.loading = true
260
+ },
261
+ rejected: (state, action) => {
262
+ state.loading = false
263
+ },
264
+ fulfilled: (state, action) => {
265
+ state.loading = false
266
+ state.todos.push(action.payload)
267
+ },
268
+ options: {
269
+ idGenerator: uuid,
270
+ },
271
+ }
272
+ )
273
+ ` ` `
274
+
275
+ :: :note
276
+
277
+ Typing for the ` create.asyncThunk ` works in the same way as [` createAsyncThunk ` ](usage /usage -with -typescript #createasyncthunk ), with one key difference .
278
+
279
+ A type for `state` and/or `dispatch` _cannot_ be provided as part of the `ThunkApiConfig`, as this would cause circular types.
280
+
281
+ Instead , it is necessary to assert the type when needed.
282
+
283
+ ` ` ` ts no-transpile
284
+ create.asyncThunk<Todo, string, { rejectValue: { error: string } }>(
285
+ async (id, thunkApi) => {
286
+ const state = thunkApi.getState() as RootState
287
+ const dispatch = thunkApi.dispatch as AppDispatch
288
+ throw thunkApi.rejectWithValue({
289
+ error: 'Oh no!',
290
+ })
291
+ }
292
+ )
293
+ ` ` `
294
+
295
+ For common thunk API configuration options , a [` withTypes ` helper ](usage / usage - with - typescript #defining - a - pre - typed - createasyncthunk ) is provided :
296
+
297
+ ` ` ` ts no-transpile
298
+ reducers: (create) => {
299
+ const createAThunk =
300
+ create.asyncThunk.withTypes<{ rejectValue: { error: string } }>()
301
+
302
+ return {
303
+ fetchTodo: createAThunk<Todo, string>(async (id, thunkApi) => {
304
+ const state = thunkApi.getState() as RootState
305
+ const dispatch = thunkApi.dispatch as AppDispatch
306
+ throw thunkApi.rejectWithValue({
307
+ error: 'Oh no!',
308
+ })
309
+ }),
310
+ fetchTodos: createAThunk<Todo[], string>(async (id, thunkApi) => {
311
+ const state = thunkApi.getState() as RootState
312
+ const dispatch = thunkApi.dispatch as AppDispatch
313
+ throw thunkApi.rejectWithValue({
314
+ error: 'Oh no, not again!',
315
+ })
316
+ }),
317
+ }
318
+ }
319
+ ` ` `
320
+
321
+ :::
322
+
133
323
### ` extraReducers `
134
324
135
325
One of the key concepts of Redux is that each slice reducer " owns" its slice of state , and that many slice reducers
0 commit comments