Skip to content

Commit e178ead

Browse files
deniswmarkerikson
authored andcommitted
Improve slice action creator types (#94)
Instead of `...args: any[]`, each action creator now expects the payload inferred from the corresponding case reducer. Fixes #93.
1 parent d8f84f7 commit e178ead

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

src/createSlice.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import { Action, AnyAction, ActionCreator, Reducer } from 'redux'
1+
import { Action, AnyAction, Reducer } from 'redux'
22
import { createAction, PayloadAction } from './createAction'
33
import { createReducer, CaseReducersMapObject } from './createReducer'
44
import { createSliceSelector, createSelectorName } from './sliceSelector'
55

6+
/**
7+
* An action creator atttached to a slice.
8+
*/
9+
export type SliceActionCreator<P> = (payload: P) => PayloadAction<P>
10+
611
export interface Slice<
712
S = any,
813
A extends Action = AnyAction,
9-
AT extends string = string
14+
AP extends { [key: string]: any } = { [key: string]: any }
1015
> {
1116
/**
1217
* The slice name.
@@ -22,7 +27,7 @@ export interface Slice<
2227
* Action creators for the types of actions that are handled by the slice
2328
* reducer.
2429
*/
25-
actions: { [type in AT]: ActionCreator<A> }
30+
actions: { [type in keyof AP]: SliceActionCreator<AP[type]> }
2631

2732
/**
2833
* Selectors for the slice reducer state. `createSlice()` inserts a single
@@ -68,6 +73,18 @@ export interface CreateSliceOptions<
6873
extraReducers?: CR2
6974
}
7075

76+
type ExtractPayloads<
77+
S,
78+
A extends PayloadAction,
79+
CR extends CaseReducersMapObject<S, A>
80+
> = {
81+
[type in keyof CR]: CR[type] extends (state: S) => any
82+
? void
83+
: (CR[type] extends (state: S, action: PayloadAction<infer P>) => any
84+
? P
85+
: never)
86+
}
87+
7188
function getType(slice: string, actionKey: string): string {
7289
return slice ? `${slice}/${actionKey}` : actionKey
7390
}
@@ -82,11 +99,11 @@ function getType(slice: string, actionKey: string): string {
8299
*/
83100
export function createSlice<
84101
S = any,
85-
A extends PayloadAction = PayloadAction,
102+
A extends PayloadAction = PayloadAction<any>,
86103
CR extends CaseReducersMapObject<S, A> = CaseReducersMapObject<S, A>
87104
>(
88105
options: CreateSliceOptions<S, A, CR>
89-
): Slice<S, A, Extract<keyof CR, string>> {
106+
): Slice<S, A, ExtractPayloads<S, A, CR>> {
90107
const { slice = '', initialState } = options
91108
const reducers = options.reducers || {}
92109
const extraReducers = options.extraReducers || {}

type-tests/files/createSlice.typetest.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,27 @@ import {
4949
// typings:expect-error
5050
const stringValue: string = slice.selectors.getCounter(0)
5151
}
52+
53+
/*
54+
* Test: Slice action creator types are inferred.
55+
*/
56+
{
57+
const counter = createSlice({
58+
slice: 'counter',
59+
initialState: 0,
60+
reducers: {
61+
increment: state => state + 1,
62+
decrement: state => state - 1,
63+
multiply: (state, action: PayloadAction<number>) => state * action.payload
64+
}
65+
})
66+
67+
counter.actions.increment()
68+
counter.actions.multiply(2)
69+
70+
// typings:expect-error
71+
counter.actions.multiply()
72+
73+
// typings:expect-error
74+
counter.actions.multiply('2')
75+
}

0 commit comments

Comments
 (0)