Let's say we have userReducer
defined like this:
function userReducer(state: string, action: UserAction): string {
switch (action.type) {
case "LOGIN":
return action.username;
case "LOGOUT":
return "";
default:
throw new Error("Unknown 'user' action");
}
}
What's the best way to define UserAction
type so it will be possible to call dispatch
both with username
payload and without:
dispatch({ type: "LOGIN", username: "Joe"}});
/* ... */
dispatch({ type: "LOGOUT" });
If type is defined like this:
type UserActionWithPayload = {
type: string;
username: string;
};
type UserActionWithoutPayload = {
type: string;
};
export type UserAction = UserActionWithPayload | UserActionWithoutPayload;
Compiler throws and error in reducer in the "LOGIN" case: TS2339: Property 'username' does not exist on type 'UserAction'. Property 'username' does not exist on type 'UserActionWithoutPayload'.
If type is defined with optional member:
export type UserAction = {
type: string;
username?: string;
}
Then compiler shows this error: TS2322: Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.
What's missing here? Maybe the whole approach is wrong?
Project uses TypeScript 3.8.3 and React.js 16.13.0.
After hours of digging and experimenting found quite an elegant solution via Typescript enum
and union types for actions:
enum UserActionType {
LOGIN = "LOGIN",
LOGOUT = "LOGOUT"
}
type UserState = string;
type UserAction =
| { type: UserActionType.LOGIN; username: string }
| { type: UserActionType.LOGOUT }
function userReducer(state: UserState, action: UserAction): string {
switch (action.type) {
case UserActionType.LOGIN:
return action.username;
case UserActionType.LOGOUT:
return "";
default:
throw new Error();
}
}
function App() {
const [user, userDispatch] = useReducer(userReducer, "");
function handleLogin() {
userDispatch({ type: UserActionType.LOGIN, username: "Joe" });
}
function handleLogout() {
userDispatch({ type: UserActionType.LOGOUT });
}
/* ... */
}
No errors or warnings using approach above, plus there is a quite strict contract for action usage.