Skip to content

Commit bdb95e7

Browse files
msutkowskiphryneas
authored andcommitted
Implement other PR comments, clarify createAsyncThunk examples
1 parent e5c26db commit bdb95e7

File tree

2 files changed

+22
-17
lines changed

2 files changed

+22
-17
lines changed

docs/api/createAsyncThunk.md

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ For example, a `type` argument of `'users/requestStatus'` will generate these ac
6464

6565
### `payloadCreator`
6666

67-
A callback function that should return a promise containing the result of some asynchronous logic. It may also return a value synchronously. If there is an error, it should return a rejected promise containing one of the following: an `Error` instance, a plain value such as a descriptive error message, or a manually rejected error with a defined payload.
67+
A callback function that should return a promise containing the result of some asynchronous logic. It may also return a value synchronously. If there is an error, it should either return a rejected promise containing an `Error` instance or a plain value such as a descriptive error message or otherwise a resolved promise with a `RejectWithValue` argument as returned by the `thunkApi.rejectWithValue` function.
6868

6969
The `payloadCreator` function can contain whatever logic you need to calculate an appropriate result. This could include a standard AJAX data fetch request, multiple AJAX calls with the results combined into a final value, interactions with React Native `AsyncStorage`, and so on.
7070

@@ -91,7 +91,7 @@ When dispatched, the thunk will:
9191
- call the `payloadCreator` callback and wait for the returned promise to settle
9292
- when the promise settles:
9393
- if the promise resolved successfully, dispatch the `fulfilled` action with the promise value as `action.payload`
94-
- if the promise resolved successfully or failed, but was returned with `rejectWithValue(value)`, dispatch the `rejected` action with the value passed into `action.payload` and 'Rejected' as `action.error.message`
94+
- if the promise resolved with a `rejectWithValue(value)` return value, dispatch the `rejected` action with the value passed into `action.payload` and 'Rejected' as `action.error.message`
9595
- if the promise failed and was not handled with `rejectWithValue`, dispatch the `rejected` action with a serialized version of the error value as `action.error`
9696
- Return a fulfilled promise containing the final dispatched action (either the `fulfilled` or `rejected` action object)
9797

@@ -391,8 +391,9 @@ import { userAPI } from './userAPI'
391391
const updateUser = createAsyncThunk(
392392
'users/update',
393393
async (userData, { rejectWithValue }) => {
394+
const { id, ...fields } = userData;
394395
try {
395-
const response = await userAPI.updateById(userData)
396+
const response = await userAPI.updateById(id, fields)
396397
return response.data.user
397398
} catch (err) {
398399
// Note: this is an example assuming the usage of axios. Other fetching libraries would likely have different implementations
@@ -409,7 +410,6 @@ const usersSlice = createSlice({
409410
name: 'users',
410411
initialState: {
411412
entities: {},
412-
loading: 'idle',
413413
error: null
414414
},
415415
reducers: {},
@@ -420,7 +420,7 @@ const usersSlice = createSlice({
420420
},
421421
[updateUser.rejected]: (state, action) => {
422422
if (action.payload) {
423-
// if a rejected action has a payload, it means that it was returned with rejectWithValue
423+
// If a rejected action has a payload, it means that it was returned with rejectWithValue
424424
state.error = action.payload.errorMessage
425425
} else {
426426
state.error = action.error
@@ -433,15 +433,16 @@ const UsersComponent = () => {
433433
const { users, loading, error } = useSelector(state => state.users)
434434
const dispatch = useDispatch()
435435

436-
const updateUser = async userData => {
437-
const resultAction = await dispatch(updateUser(userData))
436+
// This is an example of an onSubmit handler using Formik meant to demonstrate accessing the payload of the rejected action
437+
const handleUpdateUser = async (values, formikHelpers) => {
438+
const resultAction = await dispatch(updateUser(values))
438439
if (updateUser.fulfilled.match(resultAction)) {
439440
const user = unwrapResult(resultAction)
440441
showToast('success', `Updated ${user.name}`)
441442
} else {
442443
if (resultAction.payload) {
443-
// This is assuming the api returned a 400 error with a body of { errorMessage: 'Validation errors', field_errors: [{ field_name: 'Should be a string' }]}
444-
setErrors(resultAction.payload.field_errors)
444+
// This is assuming the api returned a 400 error with a body of { errorMessage: 'Validation errors', field_errors: { field_name: 'Should be a string' } }
445+
formikHelpers.setErrors(resultAction.payload.field_errors)
445446
} else {
446447
showToast('error', `Update failed: ${resultAction.error}`)
447448
}
@@ -458,6 +459,8 @@ const UsersComponent = () => {
458459
```typescript
459460
import { createAsyncThunk, createSlice, unwrapResult } from '@reduxjs/toolkit'
460461
import { userAPI } from './userAPI'
462+
import { AppDispatch, RootState } from '../store'
463+
import { FormikHelpers } from 'formik'
461464

462465
// Sample types that will be used
463466
interface User {
@@ -484,8 +487,8 @@ const updateUser = createAsyncThunk<
484487
}
485488
>('users/update', async (userData, { rejectWithValue }) => {
486489
try {
487-
const { id, ...data } = userData
488-
const response = await userAPI.updateById<UpdateUserResponse>(id, data)
490+
const { id, ...fields } = userData
491+
const response = await userAPI.updateById<UpdateUserResponse>(id, fields)
489492
return response.data.user
490493
} catch (err) {
491494
let error: AxiosError<ValidationErrors> = err // cast the error for access
@@ -512,6 +515,7 @@ const usersSlice = createSlice({
512515
initialState,
513516
reducers: {},
514517
extraReducers: builder => {
518+
// The `builder` callback form is used here because it provides correctly typed reducers from the action creators
515519
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
516520
state.entities[payload.id] = payload
517521
})
@@ -527,19 +531,20 @@ const usersSlice = createSlice({
527531
})
528532

529533
const UsersComponent = () => {
530-
const { users, loading, error } = useSelector( (state: RootState) => state.users)
531-
const dispatch = useDispatch()
534+
const { users, loading, error } = useSelector((state: RootState) => state.users)
535+
const dispatch: AppDispatch = useDispatch()
532536

533-
const updateUser = async userData => {
534-
const resultAction = await dispatch(updateUser(userData))
537+
// This is an example of an onSubmit handler using Formik meant to demonstrate accessing the payload of the rejected action
538+
const handleUpdateUser = async (values: FormValues, formikHelpers: FormikHelpers<FormValues>) => {
539+
const resultAction = await dispatch(updateUser(values))
535540
if (updateUser.fulfilled.match(resultAction)) {
536541
// user will have a type signature of User as we passed that as the Returned parameter in createAsyncThunk
537542
const user = unwrapResult(resultAction)
538543
showToast('success', `Updated ${user.name}`)
539544
} else {
540545
if (resultAction.payload) {
541546
// Being that we passed in ValidationErrors to rejectType in `createAsyncThunk`, those types will be available here.
542-
setErrors(resultAction.payload.field_errors)
547+
formikHelpers.setErrors(resultAction.payload.field_errors)
543548
} else {
544549
showToast('error', `Update failed: ${resultAction.error}`)
545550
}

docs/usage/usage-with-typescript.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ To define the types for these arguments, pass an object as the third generic arg
427427
```ts
428428
const fetchUserById = createAsyncThunk<
429429
// Return type of the payload creator
430-
Promise<MyData>,
430+
MyData,
431431
// First argument to the payload creator
432432
number,
433433
{

0 commit comments

Comments
 (0)