Skip to content

Commit 4bfa41c

Browse files
RMHonortimdorr
authored andcommitted
Add overload for bindActionCreators (#224)
* Add overload for bindActionCreators * Add ThunkActionDispatch type to use when declaring interfaces * Fix ThunkActionDispatch: generic should expect to extend a function, not just the return type
1 parent e546d62 commit 4bfa41c

File tree

4 files changed

+84
-9
lines changed

4 files changed

+84
-9
lines changed

index.d.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { Middleware, Action, AnyAction } from "redux";
1+
import {
2+
Action,
3+
ActionCreatorsMapObject,
4+
AnyAction,
5+
Dispatch,
6+
Middleware,
7+
} from 'redux';
28

39
export interface ThunkDispatch<S, E, A extends Action> {
410
<R>(thunkAction: ThunkAction<R, S, E, A>): R;
@@ -11,10 +17,33 @@ export type ThunkAction<R, S, E, A extends Action> = (
1117
extraArgument: E
1218
) => R;
1319

20+
/**
21+
* Takes a ThunkAction and returns a function signature which matches how it would appear when processed using
22+
* bindActionCreators
23+
*
24+
* @template T ThunkAction to be wrapped
25+
*/
26+
export type ThunkActionDispatch<T extends (...args: any[]) => ThunkAction<any, any, any, any>> = (...args: Parameters<T>)
27+
=> ReturnType<ReturnType<T>>;
28+
1429
export type ThunkMiddleware<S = {}, A extends Action = AnyAction, E = undefined> = Middleware<ThunkDispatch<S, E, A>, S, ThunkDispatch<S, E, A>>;
1530

1631
declare const thunk: ThunkMiddleware & {
1732
withExtraArgument<E>(extraArgument: E): ThunkMiddleware<{}, AnyAction, E>
1833
}
1934

2035
export default thunk;
36+
37+
/**
38+
* Redux behaviour changed by middleware, so overloads here
39+
*/
40+
declare module 'redux' {
41+
/**
42+
* Overload for bindActionCreators redux function, returns expects responses
43+
* from thunk actions
44+
*/
45+
function bindActionCreators<M extends ActionCreatorsMapObject<any>>(
46+
actionCreators: M,
47+
dispatch: Dispatch,
48+
): { [N in keyof M]: ReturnType<M[N]> extends ThunkAction<any, any, any, any> ? (...args: Parameters<M[N]>) => ReturnType<ReturnType<M[N]>> : M[N] }
49+
}

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"mocha": "^2.2.5",
6969
"redux": "~4.0.0",
7070
"rimraf": "^2.5.2",
71-
"typescript": "~2.6.2",
71+
"typescript": "^3.1.6",
7272
"typings-tester": "^0.3.1",
7373
"webpack": "^1.12.14"
7474
}

test/typescript.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
import { createStore, applyMiddleware } from 'redux';
2-
import thunk, { ThunkAction, ThunkMiddleware, ThunkDispatch } from '../index';
1+
import {
2+
applyMiddleware,
3+
bindActionCreators,
4+
createStore,
5+
} from 'redux';
6+
7+
import thunk, {
8+
ThunkAction,
9+
ThunkActionDispatch,
10+
ThunkDispatch,
11+
ThunkMiddleware,
12+
} from '../index';
313

414
type State = {
515
foo: string;
@@ -50,6 +60,42 @@ function anotherThunkAction(): ThunkResult<string> {
5060
}
5161
}
5262

63+
function promiseThunkAction(): ThunkResult<Promise<boolean>> {
64+
return async (dispatch, getState) => {
65+
dispatch({ type: 'FOO' });
66+
return false;
67+
}
68+
}
69+
70+
const standardAction = () => ({ type: 'FOO' });
71+
72+
interface ActionDispatchs {
73+
anotherThunkAction: ThunkActionDispatch<typeof anotherThunkAction>;
74+
promiseThunkAction: ThunkActionDispatch<typeof promiseThunkAction>;
75+
standardAction: typeof standardAction;
76+
}
77+
78+
// test that bindActionCreators correctly returns actions responses of ThunkActions
79+
// also ensure standard action creators still work as expected
80+
const actions: ActionDispatchs = bindActionCreators({
81+
anotherThunkAction,
82+
promiseThunkAction,
83+
standardAction,
84+
}, store.dispatch);
85+
86+
87+
88+
actions.anotherThunkAction() === 'hello';
89+
// typings:expect-error
90+
actions.anotherThunkAction() === false;
91+
actions.promiseThunkAction().then((res) => console.log(res));
92+
// typings:expect-error
93+
actions.promiseThunkAction().prop;
94+
actions.standardAction().type;
95+
// typings:expect-error
96+
actions.standardAction().other;
97+
98+
5399
store.dispatch({ type: 'FOO' });
54100
// typings:expect-error
55101
store.dispatch({ type: 'BAR' })
@@ -85,5 +131,5 @@ const callDispatchAsync_specificActions = (dispatch: ThunkDispatch<State, undefi
85131
const callDispatchAny = (dispatch: ThunkDispatch<State, undefined, Actions>) => {
86132
const asyncThunk = (): any => () => ({} as Promise<void>);
87133
dispatch(asyncThunk()) // result is any
88-
.then(() => console.log('done'))
89-
}
134+
.then(() => console.log('done'))
135+
}

0 commit comments

Comments
 (0)