type-safe useDispatch with redux-thunk
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