I'm using redux-thunk
to use async action creators. The result is also returned to the respective caller.
function fetchUserName(userId: number): Promise<string> {
return Promise.resolve(`User ${userId}`)
}
function requestUserName(userId: number) {
return (dispatch: Dispatch) => {
return fetchUserName(userId).then(name => {
dispatch({
type: 'SET_USERNAME',
payload: name,
})
})
}
}
This way, the store is updated, while allowing the components to handle the response directly.
function User() {
const dispatch = useDispatch()
useEffect(() => {
dispatch(requestUserName(1))
.then(name => {
console.log(`user name is ${name}`)
})
.catch(reason => {
alert('failed fetching user name')
})
}, [])
}
This is working as intended, but it will not be compiled by TypeScript due to invalid types.
dispatch
returned by useDispatch
is not recognized as a function that returns a Promise and so TypeScript argues that Property 'then' does not exist on type '(dispatch: Dispatch<AnyAction>) => Promise<void>'.
.How can this situation be solved?
It would be perfectly fine for me to create a wrapper around useDispatch
or to redefine the type of dispatch
but I have no idea how that type should look like in this particular situation.
Thank you very much for any suggestion.
useDispatch
returns the Dispatch
type used by Redux, so you can only dispatch standard actions with it. To also dispatch thunk actions, declare its type as ThunkDispatch
(from redux-thunk
).
ThunkDispatch
receives type parameters for the store state, extra thunk args and your action type. It allows to dispatch a ThunkAction
, which basically is the inner function of requestUserName
.
For example, you can type it like this:
import { ThunkDispatch } from "redux-thunk";
import { AnyAction } from "redux";
type State = { a: string }; // your state type
type AppDispatch = ThunkDispatch<State, any, AnyAction>;
// or restrict to specific actions instead of AnyAction
function User() {
const dispatch: AppDispatch = useDispatch();
useEffect(() => {
dispatch(requestUserName(1))
.then(...) // works now
}, []);
...
}
AppDispatch
can also be inferred from the store with typeof store.dispatch
:
import thunk, { ThunkDispatch, ThunkMiddleware } from "redux-thunk";
const mw: ThunkMiddleware<State, AnyAction> = thunk;
const dummyReducer = (s: State | undefined, a: AnyAction) => ({} as State);
const store = createStore(dummyReducer, applyMiddleware(mw));
type AppDispatch = typeof store.dispatch // <-- get the type from store
TS Playground sample
See also redux's documentation on using typescript with hooks: https://redux.js.org/usage/usage-with-typescript#define-typed-hooks
My common set up includes a typesafe AppDispatch
and typed hooks;
import { createStore, applyMiddleware, PreloadedState, combineReducers } from 'redux';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
const middlewares = [thunk];
const enhancer = composeWithDevTools({ /* optional actionsBlacklist, etc */ });
const reducers = combineReducers({ /* ... */ })
export type RootState = ReturnType<typeof reducers>;
export const initStore = (initState?: PreloadedState<RootState>) =>
createStore(reducers, initState, enhancer(applyMiddleware(...middlewares)));
export const store = initStore();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
type AppAction = ReturnType<typeof store.dispatch>;
export type AppDispatch = ThunkDispatch<RootState, any, AppAction>;
export const useAppDispatch = () => useDispatch<AppDispatch>();
Note it will be slightly different with Redux Toolkit as you configureStore
instead combineReducers
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