Just a kick advise for those who are struggling with the dispatch function when using thunk and hooks.
Here is an example of what am I doing to manage authentication state, fetching data from graphql server. The magic is coming when defining the dispatch Type type IAppDispatch = ThunkDispatch<IAppState, any, IAppActions>;
store.ts
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import thunkMiddleware, { ThunkDispatch, ThunkMiddleware } from "redux-thunk";
import { authReducer } from "./reducers/authReducers";
import { IAuthActions } from "./types/authTypes";
const composeEnhancers =
process.env.NODE_ENV === "development"
? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
: compose;
const rootReducer = combineReducers({
authReducer,
});
type IAppActions = IAuthActions; <-- merge here other actions
type IAppState = ReturnType<typeof rootReducer>;
type IAppDispatch = ThunkDispatch<IAppState, any, IAppActions>; <--here is the magic
const reduxStore = createStore(
rootReducer,
composeEnhancers(
applyMiddleware<IAppDispatch, any>(
thunkMiddleware as ThunkMiddleware<IAppState, IAppActions, any>
)
)
);
export { reduxStore, IAppState, IAppDispatch, IAppActions };
authActions (actions creator and dispatch thunk actions)
import { Dispatch } from "redux";
import {
loginMutation,
logoutMutation,
} from "../../components/DataComponents/Authentification/fetchAuthentification";
import { GqlSessionUser } from "../../components/DataComponents/generatedTypes";
import {
IAuthActions,
IAuthErrorAction,
IAuthLoadingAction,
IAuthLoginAction,
IAuthLogoutAction,
} from "../types/authTypes";
const authLogin = (appUserId: GqlSessionUser): IAuthLoginAction => {
return {
type: "AUTH_LOGIN",
payload: {
appUserId,
},
};
};
const authLogout = (): IAuthLogoutAction => {
return {
type: "AUTH_LOGOUT",
};
};
const authLoadingAction = (isLoading: boolean): IAuthLoadingAction => {
return {
type: "AUTH_LOADING",
payload: {
isLoading,
},
};
};
const authErrorAction = (errorMessage: string): IAuthErrorAction => {
return {
type: "AUTH_ERROR",
payload: {
errorMessage,
},
};
};
const authLoginAction = (idOrEmail: string) => {
return async (dispatch: Dispatch<IAuthActions>) => {
dispatch(authLoadingAction(true));
const { data, errors } = await loginMutation(idOrEmail); <--fetch data from GraphQl
if (data) {
dispatch(authLogin(data.login.data[0]));
}
if (errors) {
dispatch(authErrorAction(errors[0].message));
}
dispatch(authLoadingAction(false));
return true;
};
};
const authLogoutAction = () => {
return async (dispatch: Dispatch<IAuthActions>) => {
dispatch(authLoadingAction(true));
await logoutMutation(); <--fetch data from GraphQl
dispatch(authLogout());
dispatch(authLoadingAction(false));
return true;
};
};
export {
authLoginAction,
authLogoutAction,
authLoadingAction,
authErrorAction,
};
example of components that use state and dispatch async actions via useDispatch
please not how dispatch is typed as IAppDispatch, although it is imported from react-redux
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import {
authLoginAction,
authLogoutAction,
} from "../../../stateManagement/actions/authActions";
import { IAppDispatch, IAppState } from "../../../stateManagement/reduxStore";
import Button from "../../Button";
const Authentification: React.FC = (): JSX.Element => {
const dispatch: IAppDispatch = useDispatch(); <--typing here avoid "type missing" error
const isAuth = useSelector<IAppState>((state) => state.authReducer.isAuth);
const authenticate = async (idOrEmail: string): Promise<void> => {
if (!isAuth) {
dispatch(authLoginAction(idOrEmail)); <--dispatch async action through thunk
} else {
dispatch(authLogoutAction()); <--dispatch async action through thunk
}
};
return (
<Button
style={{
backgroundColor: "inherit",
color: "#FFFF",
}}
onClick={() => authenticate("[email protected]")}
>
{isAuth && <p>Logout</p>}
{!isAuth && <p>Login</p>}
</Button>
);
};
export { Authentification };