I have a reducer that does different actions depending on the action.type
, actions payload is different for certain actions.
export enum ActionType { UpdateEntireState = "UPDATE_ENTIRE_STATE", UpdateStateItem = "UPDATE_STATE_ITEM" } type TypeEditData = { id: string; name: string; surname: string; age: number; }; export type State = TypeEditData[]; export type Action = UpdateEntireState | UpdateStateItem; type UpdateEntireState = { type: ActionType.UpdateEntireState; payload: State; }; type UpdateStateItem = { type: ActionType.UpdateStateItem; payload: { id: string; data: TypeEditData }; }; export function reducer(state: State, action: Action): State { const { type, payload } = action; switch (type) { case ActionType.UpdateEntireState: { return [...payload]; } case ActionType.UpdateStateItem: { const person = state.filter((item) => item.id === payload.id); return [...state, person[0]]; } default: { throw Error("Wrong type of action!"); } } }
This code won’t work, the errors will say that my action payload can be State
or { id: string; data: TypeEditData }
.
However, if I access the payload property inside switch case using dot notation like so
return [...action.payload];
There won’t be any errors and the type guard will work fine.
How const { type, payload } = action;
differs from action.type
and action.payload
in terms of types and why doesn’t typeguard work with spread syntax?
TS version – 4.3.4
Advertisement
Answer
The issue is that you’ve defined payload
before there was type information available on action
, so it has the union type
State | { id: string; data: TypeEditData; };
Define a local variable or simply use action.payload
within each case statement and the compiler knows what type it has:
export function reducer(state: State, action: Action): State { // const { type, payload } = action; switch (action.type) { case ActionType.UpdateEntireState: { return [...action.payload]; } case ActionType.UpdateStateItem: { const person = state.filter((item) => item.id === action.payload.id); return [...state, person[0]]; } default: { throw Error("Wrong type of action!"); } } }
Variable type is established explicitly at declaration (e.g. const a: string
) or implicitly at initialization (e.g. a = 4
). Subsequent typeguard constructs are not used to re-evaluate the type of the variable. On the contrary, since the type of the variable is already defined at that point, that type is used to validate whether the later construct is valid for the variable.