diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md index de288f33d3..0498549f3f 100644 --- a/docs/usage/usage-with-typescript.md +++ b/docs/usage/usage-with-typescript.md @@ -557,6 +557,56 @@ const fetchUserById = createAsyncThunk( const lastReturnedAction = await store.dispatch(fetchUserById(3)) ``` +### Handling responses from async thunks + +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: + +#### In a reducer + +```ts +const usersSlice = createSlice({ + name: 'users', + initialState: { + entities: {}, + error: null, + }, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(updateUser.fulfilled, (state, { payload }) => { + state.entities[payload.id] = payload + }) + builder.addCase(updateUser.rejected, (state, action) => { + if (action.payload) { + // Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here. + state.error = action.payload.errorMessage + } else { + state.error = action.error + } + }) + }, +}) +``` + +#### In a component + +```ts +const handleUpdateUser = async (userData) => { + const resultAction = await dispatch(updateUser(userData)) + if (updateUser.fulfilled.match(resultAction)) { + const user = resultAction.payload + showToast('success', `Updated ${user.name}`) + } else { + if (resultAction.payload) { + // Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here. + // Note: this would also be a good place to do any handling that relies on the `rejectedWithValue` payload, such as setting field errors + showToast('error', `Update failed: ${resultAction.payload.errorMessage}`) + } else { + showToast('error', `Update failed: ${resultAction.error.message}`) + } + } +} +``` + ### Typing the `thunkApi` Object 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< 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. -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: - -- In a reducer - -```ts -const usersSlice = createSlice({ - name: 'users', - initialState: { - entities: {}, - error: null, - }, - reducers: {}, - extraReducers: (builder) => { - builder.addCase(updateUser.fulfilled, (state, { payload }) => { - state.entities[payload.id] = payload - }) - builder.addCase(updateUser.rejected, (state, action) => { - if (action.payload) { - // Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here. - state.error = action.payload.errorMessage - } else { - state.error = action.error - } - }) - }, -}) -``` - -- In a component - -```ts -const handleUpdateUser = async (userData) => { - const resultAction = await dispatch(updateUser(userData)) - if (updateUser.fulfilled.match(resultAction)) { - const user = resultAction.payload - showToast('success', `Updated ${user.name}`) - } else { - if (resultAction.payload) { - // Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here. - // Note: this would also be a good place to do any handling that relies on the `rejectedWithValue` payload, such as setting field errors - showToast('error', `Update failed: ${resultAction.payload.errorMessage}`) - } else { - showToast('error', `Update failed: ${resultAction.error.message}`) - } - } -} -``` - ### Defining a Pre-Typed `createAsyncThunk` 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`.