Skip to content

Commit cd8329c

Browse files
authored
use ThunkApiConfig for optional type arguments (#364)
1 parent 39c5b31 commit cd8329c

File tree

5 files changed

+68
-27
lines changed

5 files changed

+68
-27
lines changed

etc/redux-toolkit.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ReducersMapObject } from 'redux';
1818
import { Store } from 'redux';
1919
import { StoreEnhancer } from 'redux';
2020
import { ThunkAction } from 'redux-thunk';
21+
import { ThunkDispatch } from 'redux-thunk';
2122
import { ThunkMiddleware } from 'redux-thunk';
2223

2324
// @public
@@ -102,7 +103,7 @@ export function createAction<P = void, T extends string = string>(type: T): Payl
102103
export function createAction<PA extends PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;
103104

104105
// @alpha (undocumented)
105-
export function createAsyncThunk<Returned, ThunkArg = void, State = unknown, Extra = unknown, DispatchType extends Dispatch = Dispatch, ActionType extends string = string, ThunkAPI extends BaseThunkAPI<any, any, any> = BaseThunkAPI<State, Extra, DispatchType>>(type: ActionType, payloadCreator: (arg: ThunkArg, thunkAPI: ThunkAPI) => Promise<Returned> | Returned): ((arg: ThunkArg) => (dispatch: ThunkAPI["dispatch"], getState: ThunkAPI["getState"], extra: ThunkAPI["extra"]) => Promise<PayloadAction<undefined, string, {
106+
export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(type: string, payloadCreator: (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => Promise<Returned> | Returned): ((arg: ThunkArg) => (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<undefined, string, {
106107
arg: ThunkArg;
107108
requestId: string;
108109
aborted: boolean;

src/combinedTest.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Dispatch } from 'redux'
2-
import { createAsyncThunk, BaseThunkAPI } from './createAsyncThunk'
1+
import { createAsyncThunk } from './createAsyncThunk'
32
import { createAction, PayloadAction } from './createAction'
43
import { createSlice } from './createSlice'
54
import { configureStore } from './configureStore'
@@ -38,7 +37,9 @@ describe('Combined entity slice', () => {
3837
const fetchBooksTAC = createAsyncThunk<
3938
BookModel[],
4039
void,
41-
{ books: BooksState }
40+
{
41+
state: { books: BooksState }
42+
}
4243
>(
4344
'books/fetch',
4445
async (arg, { getState, dispatch, extra, requestId, signal }) => {

src/createAsyncThunk.ts

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { Dispatch } from 'redux'
1+
import { Dispatch, AnyAction } from 'redux'
22
import nanoid from 'nanoid'
33
import {
44
createAction,
55
PayloadAction,
66
ActionCreatorWithPreparedPayload
77
} from './createAction'
8+
import { ThunkDispatch } from 'redux-thunk'
9+
import { FallbackIfUnknown } from './tsHelpers'
810

911
// @ts-ignore we need the import of these types due to a bundling issue.
1012
type _Keep = PayloadAction | ActionCreatorWithPreparedPayload<any, unknown>
@@ -50,6 +52,39 @@ export const miniSerializeError = (value: any): any => {
5052
return value
5153
}
5254

55+
type AsyncThunkConfig = {
56+
state?: unknown
57+
dispatch?: Dispatch
58+
extra?: unknown
59+
}
60+
61+
type GetState<ThunkApiConfig> = ThunkApiConfig extends {
62+
state: infer State
63+
}
64+
? State
65+
: unknown
66+
type GetExtra<ThunkApiConfig> = ThunkApiConfig extends { extra: infer Extra }
67+
? Extra
68+
: unknown
69+
type GetDispatch<ThunkApiConfig> = ThunkApiConfig extends {
70+
dispatch: infer Dispatch
71+
}
72+
? FallbackIfUnknown<
73+
Dispatch,
74+
ThunkDispatch<
75+
GetState<ThunkApiConfig>,
76+
GetExtra<ThunkApiConfig>,
77+
AnyAction
78+
>
79+
>
80+
: ThunkDispatch<GetState<ThunkApiConfig>, GetExtra<ThunkApiConfig>, AnyAction>
81+
82+
type GetThunkAPI<ThunkApiConfig> = BaseThunkAPI<
83+
GetState<ThunkApiConfig>,
84+
GetExtra<ThunkApiConfig>,
85+
GetDispatch<ThunkApiConfig>
86+
>
87+
5388
/**
5489
*
5590
* @param type
@@ -60,20 +95,12 @@ export const miniSerializeError = (value: any): any => {
6095
export function createAsyncThunk<
6196
Returned,
6297
ThunkArg = void,
63-
State = unknown,
64-
Extra = unknown,
65-
DispatchType extends Dispatch = Dispatch,
66-
ActionType extends string = string,
67-
ThunkAPI extends BaseThunkAPI<any, any, any> = BaseThunkAPI<
68-
State,
69-
Extra,
70-
DispatchType
71-
>
98+
ThunkApiConfig extends AsyncThunkConfig = {}
7299
>(
73-
type: ActionType,
100+
type: string,
74101
payloadCreator: (
75102
arg: ThunkArg,
76-
thunkAPI: ThunkAPI
103+
thunkAPI: GetThunkAPI<ThunkApiConfig>
77104
) => Promise<Returned> | Returned
78105
) {
79106
const fulfilled = createAction(
@@ -115,9 +142,9 @@ export function createAsyncThunk<
115142

116143
function actionCreator(arg: ThunkArg) {
117144
return (
118-
dispatch: ThunkAPI['dispatch'],
119-
getState: ThunkAPI['getState'],
120-
extra: ThunkAPI['extra']
145+
dispatch: GetDispatch<ThunkApiConfig>,
146+
getState: () => GetState<ThunkApiConfig>,
147+
extra: GetExtra<ThunkApiConfig>
121148
) => {
122149
const requestId = nanoid()
123150
const abortController = new AbortController()
@@ -145,7 +172,7 @@ export function createAsyncThunk<
145172
extra,
146173
requestId,
147174
signal: abortController.signal
148-
} as ThunkAPI),
175+
}),
149176
requestId,
150177
arg
151178
)

src/tsHelpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export type IsUnknown<T, True, False = never> = unknown extends T
2020
? IsAny<T, False, True>
2121
: False
2222

23+
export type FallbackIfUnknown<T, Fallback> = IsUnknown<T, Fallback, T>
24+
2325
/**
2426
* @internal
2527
*/

type-tests/files/createAsyncThunk.typetest.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import { unwrapResult } from 'src/createAsyncThunk'
66
function expectType<T>(t: T) {
77
return t
88
}
9-
function fn() {}
9+
const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, AnyAction>
1010

11-
// basic usage
11+
// basic usage
1212
;(async function() {
13-
const dispatch = fn as ThunkDispatch<{}, any, AnyAction>
14-
1513
const async = createAsyncThunk('test', (id: number) =>
1614
Promise.resolve(id * 2)
1715
)
@@ -31,7 +29,7 @@ function fn() {}
3129
})
3230
)
3331

34-
const promise = dispatch(async(3))
32+
const promise = defaultDispatch(async(3))
3533
const result = await promise
3634

3735
if (async.fulfilled.match(result)) {
@@ -70,12 +68,20 @@ function fn() {}
7068
{ id: 'a', title: 'First' }
7169
]
7270

71+
const correctDispatch = (() => {}) as ThunkDispatch<
72+
BookModel[],
73+
{ userAPI: Function },
74+
AnyAction
75+
>
76+
7377
// Verify that the the first type args to createAsyncThunk line up right
7478
const fetchBooksTAC = createAsyncThunk<
7579
BookModel[],
7680
number,
77-
BooksState,
78-
{ userAPI: Function }
81+
{
82+
state: BooksState
83+
extra: { userAPI: Function }
84+
}
7985
>(
8086
'books/fetch',
8187
async (arg, { getState, dispatch, extra, requestId, signal }) => {
@@ -87,4 +93,8 @@ function fn() {}
8793
return fakeBooks
8894
}
8995
)
96+
97+
correctDispatch(fetchBooksTAC(1))
98+
// typings:expect-error
99+
defaultDispatch(fetchBooksTAC(1))
90100
})()

0 commit comments

Comments
 (0)