Skip to content

Commit 18c3815

Browse files
crcarrickLenz Weber
andauthored
Fix addMatcher typings (#1895)
Co-authored-by: Lenz Weber <lenz.weber@mayflower.de>
1 parent 71277de commit 18c3815

File tree

4 files changed

+42
-11
lines changed

4 files changed

+42
-11
lines changed

packages/toolkit/src/createReducer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import type { NoInfer } from './tsHelpers'
1515
*/
1616
export type Actions<T extends keyof any = string> = Record<T, Action>
1717

18+
/**
19+
* @deprecated use `TypeGuard` instead
20+
*/
1821
export interface ActionMatcher<A extends AnyAction> {
1922
(action: AnyAction): action is A
2023
}

packages/toolkit/src/mapBuilders.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import type { Action, AnyAction } from 'redux'
22
import type {
33
CaseReducer,
44
CaseReducers,
5-
ActionMatcher,
65
ActionMatcherDescriptionCollection,
76
} from './createReducer'
7+
import type { TypeGuard } from './tsHelpers'
88

99
export interface TypedActionCreator<Type extends string> {
1010
(...args: any[]): Action<Type>
@@ -96,9 +96,9 @@ const reducer = createReducer(initialState, (builder) => {
9696
});
9797
```
9898
*/
99-
addMatcher<A extends AnyAction>(
100-
matcher: ActionMatcher<A> | ((action: AnyAction) => boolean),
101-
reducer: CaseReducer<State, A>
99+
addMatcher<A>(
100+
matcher: TypeGuard<A> | ((action: any) => boolean),
101+
reducer: CaseReducer<State, A extends AnyAction ? A : A & AnyAction>
102102
): Omit<ActionReducerMapBuilder<State>, 'addCase'>
103103

104104
/**
@@ -167,9 +167,9 @@ export function executeReducerBuilderCallback<S>(
167167
actionsMap[type] = reducer
168168
return builder
169169
},
170-
addMatcher<A extends AnyAction>(
171-
matcher: ActionMatcher<A>,
172-
reducer: CaseReducer<S, A>
170+
addMatcher<A>(
171+
matcher: TypeGuard<A>,
172+
reducer: CaseReducer<S, A extends AnyAction ? A : A & AnyAction>
173173
) {
174174
if (process.env.NODE_ENV !== 'production') {
175175
if (defaultCaseReducer) {

packages/toolkit/src/tests/mapBuilders.typetest.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import type { SerializedError } from '@internal/createAsyncThunk';
1+
import type { SerializedError } from '@internal/createAsyncThunk'
22
import { createAsyncThunk } from '@internal/createAsyncThunk'
33
import { executeReducerBuilderCallback } from '@internal/mapBuilders'
44
import type { AnyAction } from '@reduxjs/toolkit'
55
import { createAction } from '@reduxjs/toolkit'
6-
import { expectType } from './helpers'
6+
import { expectExactType, expectType } from './helpers'
77

88
/** Test: alternative builder callback for actionMap */
99
{
@@ -56,10 +56,34 @@ import { expectType } from './helpers'
5656
expectType<ReturnType<typeof increment>>(action)
5757
})
5858

59+
{
60+
// action type is inferred when type predicate lacks `type` property
61+
type PredicateWithoutTypeProperty = {
62+
payload: number
63+
}
64+
65+
builder.addMatcher(
66+
(action): action is PredicateWithoutTypeProperty => true,
67+
(state, action) => {
68+
expectType<PredicateWithoutTypeProperty>(action)
69+
expectType<AnyAction>(action)
70+
}
71+
)
72+
}
73+
5974
// action type defaults to AnyAction if no type predicate matcher is passed
6075
builder.addMatcher(
6176
() => true,
6277
(state, action) => {
78+
expectExactType({} as AnyAction)(action)
79+
}
80+
)
81+
82+
// with a boolean checker, action can also be typed by type argument
83+
builder.addMatcher<{ foo: boolean }>(
84+
() => true,
85+
(state, action) => {
86+
expectType<{ foo: boolean }>(action)
6387
expectType<AnyAction>(action)
6488
}
6589
)

packages/toolkit/src/tsHelpers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,12 @@ export type NoInfer<T> = [T][T extends any ? 0 : never]
101101

102102
export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
103103

104+
export interface TypeGuard<T> {
105+
(value: any): value is T
106+
}
107+
104108
export interface HasMatchFunction<T> {
105-
match: (v: any) => v is T
109+
match: TypeGuard<T>
106110
}
107111

108112
export const hasMatchFunction = <T>(
@@ -112,7 +116,7 @@ export const hasMatchFunction = <T>(
112116
}
113117

114118
/** @public */
115-
export type Matcher<T> = HasMatchFunction<T> | ((v: any) => v is T)
119+
export type Matcher<T> = HasMatchFunction<T> | TypeGuard<T>
116120

117121
/** @public */
118122
export type ActionFromMatcher<M extends Matcher<any>> = M extends Matcher<

0 commit comments

Comments
 (0)