Skip to content

Commit cba147f

Browse files
authored
Improve doc clarity around TypeScript and createAsyncThunk usage
I had a difficult time figuring out how to get proper TypeScript responses for dispatched thunk actions. I read the docs several times, and even though the answer was there (use type narrowing via match), I missed it each time. I think I skipped it because the relevant information was nested under the "Manually defining thunkAPI types" category, which I wasn't doing in my case. Since this type narrowing is very useful and relevant even when you're not manually typing `thunkAPI`, I'm making a proposal in this PR to move this information slightly higher in the doc and give it a dedicated section. For more context, see my question [on Discord](https://discord.com/channels/102860784329052160/103538784460615680/1264479048524890224). I hope you'll consider making this small change. Thank you!
1 parent a9362fb commit cba147f

File tree

1 file changed

+50
-48
lines changed

1 file changed

+50
-48
lines changed

docs/usage/usage-with-typescript.md

+50-48
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,56 @@ const fetchUserById = createAsyncThunk(
557557
const lastReturnedAction = await store.dispatch(fetchUserById(3))
558558
```
559559

560+
### Handling responses from async thunks
561+
562+
You can leverage checks against `action.payload` and `match` as provided by `createAction` as a type-guard for when you want to access known properties on defined types. Example:
563+
564+
#### In a reducer
565+
566+
```ts
567+
const usersSlice = createSlice({
568+
name: 'users',
569+
initialState: {
570+
entities: {},
571+
error: null,
572+
},
573+
reducers: {},
574+
extraReducers: (builder) => {
575+
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
576+
state.entities[payload.id] = payload
577+
})
578+
builder.addCase(updateUser.rejected, (state, action) => {
579+
if (action.payload) {
580+
// Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here.
581+
state.error = action.payload.errorMessage
582+
} else {
583+
state.error = action.error
584+
}
585+
})
586+
},
587+
})
588+
```
589+
590+
#### In a component
591+
592+
```ts
593+
const handleUpdateUser = async (userData) => {
594+
const resultAction = await dispatch(updateUser(userData))
595+
if (updateUser.fulfilled.match(resultAction)) {
596+
const user = resultAction.payload
597+
showToast('success', `Updated ${user.name}`)
598+
} else {
599+
if (resultAction.payload) {
600+
// Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here.
601+
// Note: this would also be a good place to do any handling that relies on the `rejectedWithValue` payload, such as setting field errors
602+
showToast('error', `Update failed: ${resultAction.payload.errorMessage}`)
603+
} else {
604+
showToast('error', `Update failed: ${resultAction.error.message}`)
605+
}
606+
}
607+
}
608+
```
609+
560610
### Typing the `thunkApi` Object
561611

562612
The second argument to the `payloadCreator`, known as `thunkApi`, is an object containing references to the `dispatch`, `getState`, and `extra` arguments from the thunk middleware as well as a utility function called `rejectWithValue`. If you want to use these from within the `payloadCreator`, you will need to define some generic arguments, as the types for these arguments cannot be inferred. Also, as TS cannot mix explicit and inferred generic parameters, from this point on you'll have to define the `Returned` and `ThunkArg` generic parameter as well.
@@ -657,54 +707,6 @@ const updateUser = createAsyncThunk<
657707

658708
While this notation for `state`, `dispatch`, `extra` and `rejectValue` might seem uncommon at first, it allows you to provide only the types for these you actually need - so for example, if you are not accessing `getState` within your `payloadCreator`, there is no need to provide a type for `state`. The same can be said about `rejectValue` - if you don't need to access any potential error payload, you can ignore it.
659709

660-
In addition, you can leverage checks against `action.payload` and `match` as provided by `createAction` as a type-guard for when you want to access known properties on defined types. Example:
661-
662-
- In a reducer
663-
664-
```ts
665-
const usersSlice = createSlice({
666-
name: 'users',
667-
initialState: {
668-
entities: {},
669-
error: null,
670-
},
671-
reducers: {},
672-
extraReducers: (builder) => {
673-
builder.addCase(updateUser.fulfilled, (state, { payload }) => {
674-
state.entities[payload.id] = payload
675-
})
676-
builder.addCase(updateUser.rejected, (state, action) => {
677-
if (action.payload) {
678-
// Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here.
679-
state.error = action.payload.errorMessage
680-
} else {
681-
state.error = action.error
682-
}
683-
})
684-
},
685-
})
686-
```
687-
688-
- In a component
689-
690-
```ts
691-
const handleUpdateUser = async (userData) => {
692-
const resultAction = await dispatch(updateUser(userData))
693-
if (updateUser.fulfilled.match(resultAction)) {
694-
const user = resultAction.payload
695-
showToast('success', `Updated ${user.name}`)
696-
} else {
697-
if (resultAction.payload) {
698-
// Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here.
699-
// Note: this would also be a good place to do any handling that relies on the `rejectedWithValue` payload, such as setting field errors
700-
showToast('error', `Update failed: ${resultAction.payload.errorMessage}`)
701-
} else {
702-
showToast('error', `Update failed: ${resultAction.error.message}`)
703-
}
704-
}
705-
}
706-
```
707-
708710
### Defining a Pre-Typed `createAsyncThunk`
709711

710712
As of RTK 1.9, you can define a "pre-typed" version of `createAsyncThunk` that can have the types for `state`, `dispatch`, and `extra` built in. This lets you set up those types once, so you don't have to repeat them each time you call `createAsyncThunk`.

0 commit comments

Comments
 (0)