Hur man skickar ThunkAction med redux-thunk och TypeScript
Jag har problem med att skicka en redux-thunk-åtgärd med Typescript.
import { AnyAction, applyMiddleware, createStore } from 'redux'
import thunk, { ThunkAction } from 'redux-thunk'
interface State {
counter: number
}
const initialState: State = {
counter: 10
}
function reducer(state = initialState, action: AnyAction) {
switch (action.type) {
case 'increment':
return { counter: action.payload }
default:
return state
}
}
function increment(): ThunkAction<void, State, unknown, AnyAction> {
return async function (dispatch) {
dispatch({
type: 'increment',
payload: 20
})
}
}
const store = createStore(reducer, applyMiddleware(thunk))
store.dispatch(increment())
Detta är felet som jag får:
Argument of type 'ThunkAction<void, State, unknown, AnyAction>' is not assignable to parameter of type 'AnyAction'.
Property 'type' is missing in type 'ThunkAction<void, State, unknown, AnyAction>' but required in type 'AnyAction'.
Jag har försökt flera olika saker för handlingstyp såsom anpassat gränssnitt, Action etc. och inget fungerar.
Svar #1
Standardtypen dispatch känner inte till thunk, eftersom typerna i "base redux" inte är särskilt kraftfulla. Så du måste manuellt konvertera den till ThunkDispatch:
(store.dispatch as ThunkDispatch<State, unknown, AnyAction>)(increment())
Bara som en PSA: Den typ av redux du skriver här (vanilj-redux med handskrivna actions, action-types, switch-case-satser och immutabel logik i reducers) är inte längre det "officiellt rekommenderade tillvägagångssättet" för att skriva redux. Ta gärna en titt på redux toolkit och följ helst de officiella, uppdaterade redux-handledningarna, eftersom du mest troligt följer en väldigt föråldrad.
Redux Toolkit är också mycket lättare att använda i allmänhet, och speciellt med TypeScript (och store.dispatch kommer att ha rätt typ om du använder det ;) )
Svar #2
Bara ett litet tips till dem som har problem med dispatch-funktionen när de använder thunk och hooks.
Här är ett exempel på vad jag gör för att hantera autentiseringstillstånd och hämta data från GraphQL-servern. Magin kommer när man definierar dispatch-typen 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 (åtgärdsskapare och skicka thunk-åtgärder)
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,
};
exempel på komponenter som använder state och skickar asynkrona åtgärder via useDispatch
notera hur dispatch är typad som IAppDispatch, även om det importeras från 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 ")}
>
{isAuth && <p>Logout</p>}
{!isAuth && <p>Login</p>}
</Button>
);
};
export { Authentification };
