Matching Utilities
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.
#
General PurposeisAllOf
- returns true when all conditions are metisAnyOf
- returns true when at least one of the conditions are met
createAsyncThunk
-specific matchers#
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
isAllOf
#
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.
isAnyOf
#
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.
isAsyncThunkAction
#
A higher-order function that returns a type guard function that may be used to check whether an action was created by createAsyncThunk
.
- TypeScript
- JavaScript
import { isAsyncThunkAction, AnyAction } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARequestAction = isAsyncThunkAction(requestThunk1, requestThunk2)
function handleRequestAction(action: AnyAction) { if (isARequestAction(action)) { // action is an action dispatched by either `requestThunk1` or `requestThunk2` }}
import { isAsyncThunkAction } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARequestAction = isAsyncThunkAction(requestThunk1, requestThunk2)
function handleRequestAction(action) { if (isARequestAction(action)) { // action is an action dispatched by either `requestThunk1` or `requestThunk2` }}
isPending
#
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.
- TypeScript
- JavaScript
import { isPending, AnyAction } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isAPendingAction = isPending(requestThunk1, requestThunk2)
function handlePendingAction(action: AnyAction) { if (isAPendingAction(action)) { // action is a pending action dispatched by either `requestThunk1` or `requestThunk2` }}
import { isPending } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isAPendingAction = isPending(requestThunk1, requestThunk2)
function handlePendingAction(action) { if (isAPendingAction(action)) { // action is a pending action dispatched by either `requestThunk1` or `requestThunk2` }}
isFulfilled
#
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.
- TypeScript
- JavaScript
import { isFulfilled, AnyAction } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isAFulfilledAction = isFulfilled(requestThunk1, requestThunk2)
function handleFulfilledAction(action: AnyAction) { if (isAFulfilledAction(action)) { // action is a fulfilled action dispatched by either `requestThunk1` or `requestThunk2` }}
import { isFulfilled } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isAFulfilledAction = isFulfilled(requestThunk1, requestThunk2)
function handleFulfilledAction(action) { if (isAFulfilledAction(action)) { // action is a fulfilled action dispatched by either `requestThunk1` or `requestThunk2` }}
isRejected
#
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.
- TypeScript
- JavaScript
import { isRejected, AnyAction } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARejectedAction = isRejected(requestThunk1, requestThunk2)
function handleRejectedAction(action: AnyAction) { if (isARejectedAction(action)) { // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2` }}
import { isRejected } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARejectedAction = isRejected(requestThunk1, requestThunk2)
function handleRejectedAction(action) { if (isARejectedAction(action)) { // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2` }}
isRejectedWithValue
#
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
.
- TypeScript
- JavaScript
import { isRejectedWithValue, AnyAction } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARejectedWithValueAction = isRejectedWithValue( requestThunk1, requestThunk2)
function handleRejectedWithValueAction(action: AnyAction) { if (isARejectedWithValueAction(action)) { // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2` // where rejectWithValue was used }}
import { isRejectedWithValue } from '@reduxjs/toolkit'import { requestThunk1, requestThunk2 } from '@virtual/matchers'
const isARejectedWithValueAction = isRejectedWithValue( requestThunk1, requestThunk2)
function handleRejectedWithValueAction(action) { if (isARejectedWithValueAction(action)) { // action is a rejected action dispatched by either `requestThunk1` or `requestThunk2` // where rejectWithValue was used }}
#
Using matchers to reduce code complexity, duplication and boilerplateWhen 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:
- TypeScript
- JavaScript
import { createAsyncThunk, createReducer, 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,} as ExampleState
export const isSpecialAndInterestingThunk = createAsyncThunk( 'isSpecialAndInterestingThunk', () => { return { isSpecial: true, isInteresting: true, } })
// This has unnecessary complexityconst loadingReducer = createReducer(initialState, (builder) => { builder.addCase(isSpecialAndInterestingThunk.fulfilled, (state, action) => { if (isSpecial(action)) { state.isSpecial = true } if (isInteresting(action)) { state.isInteresting = true } })})
import { createAsyncThunk, createReducer } from '@reduxjs/toolkit'
function isSpecial(action) { return action.payload.isSpecial}
function isInteresting(action) { return action.payload.isInteresting}
const initialState = { isSpecial: false, isInteresting: false,}
export const isSpecialAndInterestingThunk = createAsyncThunk( 'isSpecialAndInterestingThunk', () => { return { isSpecial: true, isInteresting: true, } })
// This has unnecessary complexityconst 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.
- TypeScript
- JavaScript
import { createReducer, isAllOf } from '@reduxjs/toolkit'import { isSpecialAndInterestingThunk, initialState, isSpecial, isInteresting, 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 } )})
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
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 } )})
#
Using matchers as a TypeScript Type GuardThe function returned by isAllOf
and isAnyOf
can also be used as a TypeScript type guard in other contexts.
- TypeScript
- JavaScript
import { isAllOf, PayloadAction } from '@reduxjs/toolkit'import { Data, isSpecial, isInteresting } 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 { isAllOf } from '@reduxjs/toolkit'import { isSpecial, isInteresting } from '@virtual/matchers' // This is a fake pkg that provides the types shown above
const isSpecialAndInteresting = isAllOf(isSpecial, isInteresting)
function someFunction(action) { if (isSpecialAndInteresting(action)) { // "action" will be correctly typed as: // `PayloadAction<Special> & PayloadAction<Interesting>` }}
- TypeScript
- JavaScript
import { isAnyOf, 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>` }}
import { isAnyOf } from '@reduxjs/toolkit'import { isSpecial, isInteresting } from '@virtual/matchers' // this is a fake pkg that provides the types shown above
const isSpecialOrInteresting = isAnyOf(isSpecial, isInteresting)
function someFunction(action) { if (isSpecialOrInteresting(action)) { // "action" will be correctly typed as: // `PayloadAction<Special> | PayloadAction<Interesting>` }}