Skip to content

Commit feaf9ac

Browse files
authored
Better generic argument for default Thunk Middleware (#329)
* fix #321 * better type for AppThunk in advanced tutorial * keep backwards-compatiblity with ThunkActions with a `null` ExtraArgument * small tweak, add type test * lint
1 parent 1774987 commit feaf9ac

File tree

5 files changed

+30
-7
lines changed

5 files changed

+30
-7
lines changed

docs/tutorials/advanced-tutorial.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ Before we go any further, let's add a type declaration we can reuse instead.
541541

542542
export type AppDispatch = typeof store.dispatch
543543

544-
+export type AppThunk = ThunkAction<void, RootState, null, Action<string>>
544+
+export type AppThunk = ThunkAction<void, RootState, unknown, Action<string>>
545545
```
546546

547547
The `AppThunk` type declares that the "action" that we're using is specifically a thunk function. The thunk is customized with some additional type parameters:

etc/redux-toolkit.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export type CaseReducerWithPrepare<State, Action extends PayloadAction> = {
8181
export type ConfigureEnhancersCallback = (defaultEnhancers: StoreEnhancer[]) => StoreEnhancer[];
8282

8383
// @public
84-
export function configureStore<S = any, A extends Action = AnyAction, M extends Middlewares<S> = [ThunkMiddleware<S>]>(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M>;
84+
export function configureStore<S = any, A extends Action = AnyAction, M extends Middlewares<S> = [ThunkMiddlewareFor<S>]>(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M>;
8585

8686
// @public
8787
export interface ConfigureStoreOptions<S = any, A extends Action = AnyAction, M extends Middlewares<S> = Middlewares<S>> {

src/configureStore.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import {
1717
composeWithDevTools,
1818
EnhancerOptions as DevToolsOptions
1919
} from 'redux-devtools-extension'
20-
import { ThunkMiddleware } from 'redux-thunk'
2120

2221
import isPlainObject from './isPlainObject'
23-
import { getDefaultMiddleware } from './getDefaultMiddleware'
22+
import {
23+
getDefaultMiddleware,
24+
ThunkMiddlewareFor
25+
} from './getDefaultMiddleware'
2426
import { DispatchForMiddlewares } from './tsHelpers'
2527

2628
const IS_PRODUCTION = process.env.NODE_ENV === 'production'
@@ -120,7 +122,7 @@ export interface EnhancedStore<
120122
export function configureStore<
121123
S = any,
122124
A extends Action = AnyAction,
123-
M extends Middlewares<S> = [ThunkMiddleware<S>]
125+
M extends Middlewares<S> = [ThunkMiddlewareFor<S>]
124126
>(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M> {
125127
const {
126128
reducer = undefined,

src/getDefaultMiddleware.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,18 @@ interface GetDefaultMiddlewareOptions {
2828
serializableCheck?: boolean | SerializableStateInvariantMiddlewareOptions
2929
}
3030

31-
type ThunkMiddlewareFor<S, O extends GetDefaultMiddlewareOptions> = O extends {
31+
export type ThunkMiddlewareFor<
32+
S,
33+
O extends GetDefaultMiddlewareOptions = {}
34+
> = O extends {
3235
thunk: false
3336
}
3437
? never
3538
: O extends { thunk: { extraArgument: infer E } }
3639
? ThunkMiddleware<S, AnyAction, E>
37-
: ThunkMiddleware<S>
40+
:
41+
| ThunkMiddleware<S, AnyAction, null> //The ThunkMiddleware with a `null` ExtraArgument is here to provide backwards-compatibility.
42+
| ThunkMiddleware<S, AnyAction>
3843

3944
/**
4045
* Returns any array containing the default middleware installed by

type-tests/files/configureStore.typetest.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,20 @@ import thunk, { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk'
254254
const result2: 'B' = store.dispatch('b')
255255
const result3: Promise<'A'> = store.dispatch(thunkA())
256256
}
257+
/**
258+
* Accepts thunk with `unknown`, `undefined` or `null` ThunkAction extraArgument per default
259+
*/
260+
{
261+
const store = configureStore({ reducer: {} })
262+
// undefined is the default value for the ThunkMiddleware extraArgument
263+
store.dispatch(function() {} as ThunkAction<void, {}, undefined, AnyAction>)
264+
// null was previously documented in the redux docs
265+
store.dispatch(function() {} as ThunkAction<void, {}, null, AnyAction>)
266+
// unknown is the best way to type a ThunkAction if you do not care
267+
// about the value of the extraArgument, as it will always work with every
268+
// ThunkMiddleware, no matter the actual extraArgument type
269+
store.dispatch(function() {} as ThunkAction<void, {}, unknown, AnyAction>)
270+
// typings:expect-error
271+
store.dispatch(function() {} as ThunkAction<void, {}, boolean, AnyAction>)
272+
}
257273
}

0 commit comments

Comments
 (0)