id | title | sidebar_label | hide_title |
---|---|---|---|
matching-utilities |
Matching Utilities |
Matching Utilities |
true |
Redux Toolkit exports several type-safe action matching utilities that you can leverage when checking for specific kinds of actions. These are primarily useful for the builder.addMatcher()
cases in createSlice
and createReducer
, as well as when writing custom middleware.
isAllOf
- returns true when all conditions are metisAnyOf
- returns true when at least one of the conditions are met
All these matchers can either be called with one or more thunks as arguments, in which case they will return a matcher function for that condition and thunks, or with one actions, in which case they will match for any thunk action with said condition.
isAsyncThunkAction
- accepts one or more action creators and returns true when all matchisPending
- accepts one or more action creators and returns true when all matchisFulfilled
- accepts one or more action creators and returns true when all matchisRejected
- accepts one or more action creators and returns true when all matchisRejectedWithValue
- accepts one or more action creators and returns true when all match
A higher-order function that accepts one or more of:
redux-toolkit
action creator functions such as the ones produced by:- type guard functions
- custom action creator functions that have a
.match
property that is a type guard
It will return a type guard function that returns true
if all of the provided functions match.
Accepts the same inputs as isAllOf
and will return a type guard function that returns true
if at least one of the provided functions match.
A higher-order function that returns a type guard function that may be used to check whether an action was created by createAsyncThunk
.
import { isAsyncThunkAction } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARequestAction = isAsyncThunkAction(requestThunk1, requestThunk2)
function handleRequestAction(action: UnknownAction) {
if (isARequestAction(action)) {
// action is an action dispatched by either `requestThunk1` or `requestThunk2`
}
}
A higher-order function that returns a type guard function that may be used to check whether an action is a 'pending' action creator from the createAsyncThunk
promise lifecycle.
import { isPending } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isAPendingAction = isPending(requestThunk1, requestThunk2)
function handlePendingAction(action: UnknownAction) {
if (isAPendingAction(action)) {
// action is a pending action dispatched by either `requestThunk1` or `requestThunk2`
}
}
A higher-order function that returns a type guard function that may be used to check whether an action is a 'fulfilled'' action creator from the createAsyncThunk
promise lifecycle.
import { isFulfilled } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isAFulfilledAction = isFulfilled(requestThunk1, requestThunk2)
function handleFulfilledAction(action: UnknownAction) {
if (isAFulfilledAction(action)) {
// action is a fulfilled action dispatched by either `requestThunk1` or `requestThunk2`
}
}
A higher-order function that returns a type guard function that may be used to check whether an action is a 'rejected' action creator from the createAsyncThunk
promise lifecycle.
import { isRejected } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARejectedAction = isRejected(requestThunk1, requestThunk2)
function handleRejectedAction(action: UnknownAction) {
if (isARejectedAction(action)) {
// action is a rejected action dispatched by either `requestThunk1` or `requestThunk2`
}
}
A higher-order function that returns a type guard function that may be used to check whether an action is a 'rejected' action creator from the createAsyncThunk
promise lifecycle that was created by rejectWithValue
.
import { isRejectedWithValue } from '@reduxjs/toolkit'
import type { UnknownAction } from '@reduxjs/toolkit'
import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARejectedWithValueAction = isRejectedWithValue(
requestThunk1,
requestThunk2,
)
function handleRejectedWithValueAction(action: UnknownAction) {
if (isARejectedWithValueAction(action)) {
// action is a rejected action dispatched by either `requestThunk1` or `requestThunk2`
// where rejectWithValue was used
}
}
When using the builder
pattern to construct a reducer, we add cases or matchers one at a time. However, by using isAnyOf
or isAllOf
,
we're able to easily use the same matcher for several cases in a type-safe manner.
First, let's examine an unnecessarily complex example:
import { createAsyncThunk, createReducer } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
interface Data {
isInteresting: boolean
isSpecial: boolean
}
interface Special extends Data {
isSpecial: true
}
interface Interesting extends Data {
isInteresting: true
}
function isSpecial(
action: PayloadAction<Data>,
): action is PayloadAction<Special> {
return action.payload.isSpecial
}
function isInteresting(
action: PayloadAction<Data>,
): action is PayloadAction<Interesting> {
return action.payload.isInteresting
}
interface ExampleState {
isSpecial: boolean
isInteresting: boolean
}
const initialState = {
isSpecial: false,
isInteresting: false,
} satisfies ExampleState as ExampleState
export const isSpecialAndInterestingThunk = createAsyncThunk(
'isSpecialAndInterestingThunk',
() => {
return {
isSpecial: true,
isInteresting: true,
}
},
)
// This has unnecessary complexity
const loadingReducer = createReducer(initialState, (builder) => {
builder.addCase(isSpecialAndInterestingThunk.fulfilled, (state, action) => {
if (isSpecial(action)) {
state.isSpecial = true
}
if (isInteresting(action)) {
state.isInteresting = true
}
})
})
In this scenario, we can use isAllOf
to simplify our code and reduce some of the boilerplate.
import { createReducer, isAllOf } from '@reduxjs/toolkit'
import {
isSpecialAndInterestingThunk,
initialState,
isSpecial,
isInteresting,
} from '@virtual/matchers' // This is a fake pkg that provides the types shown above
import type { Data } from '@virtual/matchers' // This is a fake pkg that provides the types shown above
const loadingReducer = createReducer(initialState, (builder) => {
builder
.addMatcher(
isAllOf(isSpecialAndInterestingThunk.fulfilled, isSpecial),
(state, action) => {
state.isSpecial = true
},
)
.addMatcher(
isAllOf(isSpecialAndInterestingThunk.fulfilled, isInteresting),
(state, action) => {
state.isInteresting = true
},
)
})
The function returned by isAllOf
and isAnyOf
can also be used as a TypeScript type guard in other contexts.
import { isAllOf } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { isSpecial, isInteresting } from '@virtual/matchers' // This is a fake pkg that provides the types shown above
import type { Data } from '@virtual/matchers' // This is a fake pkg that provides the types shown above
const isSpecialAndInteresting = isAllOf(isSpecial, isInteresting)
function someFunction(action: PayloadAction<Data>) {
if (isSpecialAndInteresting(action)) {
// "action" will be correctly typed as:
// `PayloadAction<Special> & PayloadAction<Interesting>`
}
}
import { isAnyOf } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
import { Data, isSpecial, isInteresting } from '@virtual/matchers' // this is a fake pkg that provides the types shown above
const isSpecialOrInteresting = isAnyOf(isSpecial, isInteresting)
function someFunction(action: PayloadAction<Data>) {
if (isSpecialOrInteresting(action)) {
// "action" will be correctly typed as:
// `PayloadAction<Special> | PayloadAction<Interesting>`
}
}