Complete working codesandbox example here
I declare a simple action type and an asynchronous thunk that dispatches it:
type ActionType = { type: "foo"; v: number };
const FooThunk: ThunkAction<Promise<ActionType>, any, any, ActionType> = (dispatch): Promise<ActionType>
=> {
return new Promise<number>((resolve) => {
setTimeout(() => {
resolve(42);
}, 100);
}).then((v: number) => {
return dispatch({ type: "foo", v });
});
};
The question is now what is the type of the value I get when I call dispatch(FooThunk)
.
Typescript thinks the type is ThunkAction<Promise<ActionType>, any, any, ActionType>
and complains (on line 38 of the sandbox) with the following message:
'ThunkAction<Promise<ActionType>, any, any, ActionType>' is missing the following properties from type 'Promise<ActionType>': then, catch, [Symbol.toStringTag]ts(2739)
However, when I log the value I get at runtime (line 48 of the codesandbox) I see clearly that it is a Promise
. Searching on StackOverflow I have found contradictory answers.
This answer says that dispatching a thunk returns the thunk itself. Whereas this answer suggests that dispatching a thunk returns a Promise.
Typescript's type system seems to say that the type of dispatching a thunk is the same as the thunk itself. However at runtime I get a Promise object. What am I missing?
For completeness purposes only, I append the code that you will find the the sandbox (link provided above):
import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import { initialState, rootReducer } from "./rootReducer";
import "./styles.css";
import { ThunkDispatch as Dispatch, ThunkAction } from "redux-thunk";
import { connect, ConnectedProps } from "react-redux";
import { applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(
rootReducer /* preloadedState, */,
applyMiddleware(thunk)
);
//const store = createStore(rootReducer, initialState);
type ActionType = { type: "foo"; v: number };
const FooThunk: ThunkAction<Promise<ActionType>, any, any, ActionType> = (
dispatch
): Promise<ActionType> => {
return new Promise<number>((resolve) => {
setTimeout(() => {
resolve(42);
}, 100);
}).then((v: number) => {
return dispatch({ type: "foo", v });
});
};
const mapDispatchToProps = (dispatch: Dispatch<any, any, any>) => {
return {
dispatchFooThunk: (): Promise<ActionType> => dispatch(FooThunk)
};
};
const connector = connect(null, mapDispatchToProps);
type PropsFromRedux = ConnectedProps<typeof connector>;
class FooComponent_ extends React.Component<PropsFromRedux> {
componentDidMount() {
const p = this.props.dispatchFooThunk();
console.log(p); // if you examine log output you see clearly that this is a PROMISE !
}
render() {
return <div>foo</div>;
}
}
const FooComponent = connector(FooComponent_);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<FooComponent />
</Provider>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
The return value of a thunk is the return value of the thunk action itself. As a consequence: If it's an async function, it returns a Promise.
The normal Dispatch
type does not know how to handle thunks though.
If you are using redux toolkit this documentation part describes how to properly type your useDispatch
hook with a Dispatch type inferred from the store configuration.
If you are using plain redux, you just can use the ThunkDispatch
type. This part of the react-redux documentation describes how to type your useDispatch
hook in that case.
PS: in your specific case, your ThunkDispatch<any, any, any>
is too generic. That last any
will match too eagerly. Try a ThunkDispatch<any, any, AnyAction>
instead, that will work.