I have a redux thunk action that fetches some data and then dispatches some actions (not shown in the code here, but you'll be able to find it in the demo link bellow)
export const fetchPosts = (id: string) => (dispatch: Dispatch<TActions>) => {
return fetch('http://example.com').then(
response => {
return response.json().then(json => {
return "Success message";
});
},
err => {
throw err;
}
);
};
and than in my component I use mapDispatchToProps
with bindActionCreators
to call this function from my component like so:
public fetchFunc() {
this.props.fetchPosts("test").then(
res => {
console.log("Res from app", res);
},
err => {
console.log("Err from app", err);
}
);
}
Since I am using typescript, I need to define the type of this function in the Props
interface IProps {
name?: string;
posts: IPost[];
loading: boolean;
fetchPosts: (id: string) => Promise<string | Error>;
}
If I do-it like above, Typescript will complain that I should do-it like this:
fetchPosts: (id: string) => (dispatch: Dispatch<TActions>) => Promise<string | Error>;
If I do-it like this, then Typescript complains when I use then
in my component saying that that function is not a promise.
I created a demo where you can fiddle with the code
Pressing "Load from remote" will sometimes fail just to see if the promise:
https://codesandbox.io/s/v818xwl670
The problem is the call to bindActionCreators
in mapDispatchToProps
. At runtime bindActionCreators
basically transforms this (id: string) => (dispatch: Dispatch<TActions>) => Promise<string>;
into this (id: string) => Promise<string>;
, but the type for bindActionCreators
does not reflect this transformation. This is probably due to the fact that to accomplish this you would need conditional types which until recently were not available.
If we look at this sample usage from the redux repo, we see that they accomplish the transformation by specifying the types of the functions explicitly:
const boundAddTodoViaThunk = bindActionCreators<
ActionCreator<AddTodoThunk>,
ActionCreator<AddTodoAction>
>(addTodoViaThunk, dispatch)
We could do the same in your code, referencing existing types, but this hurts type safety as there is no check that fetchPosts
in the two types will be correctly typed:
const mapDispatchToProps = (dispatch: Dispatch<TActions>): Partial<IProps> =>
bindActionCreators<{ fetchPosts: typeof fetchPosts }, Pick<IProps, 'fetchPosts'>>(
{
fetchPosts
},
dispatch
);
Or we could use a type assertion since the above method does not really offer any safety anyway:
const mapDispatchToProps2 = (dispatch: Dispatch<TActions>) =>
bindActionCreators({
fetchPosts: fetchPosts as any as ((id: string) => Promise<string>)
}, dispatch );
For a truly type safe way to do this we need to use typescript 2.8 and conditional types with a helper function. We can type bindActionCreators
the way it should, and automatically infer the correct type for the resulting creators:
function mybindActionCreators<M extends ActionCreatorsMapObject>(map: M, dispatch: Dispatch<TActions>) {
return bindActionCreators<M, { [P in keyof M] : RemoveDispatch<M[P]> }>(map, dispatch);
}
const mapDispatchToProps = (dispatch: Dispatch<TActions>) =>
mybindActionCreators(
{
fetchPosts
},
dispatch
);
// Helpers
type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type RemoveDispatch<T extends Function> =
T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => (dispatch: Dispatch<any>) => infer R ? (
IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => R :
IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => R :
IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => R :
IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => R :
IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => R :
IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => R :
IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => R :
IsValidArg<C> extends true ? (a: A, b: B, c: C) => R :
IsValidArg<B> extends true ? (a: A, b: B) => R :
IsValidArg<A> extends true ? (a: A) => R :
() => R
) : T;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With