Reputation: 6241
I have a union type Actions
which is
type Actions = Readonly<{
type: ActionTypes.LOAD_POST;
payload: string;
}> | Readonly<{
type: ActionTypes.LOAD_POST_FAIL;
payload: string;
}> | Readonly<{
type: ActionTypes.LOAD_POST_SUCCESS;
payload: {
url: string;
post: Post;
};
}>
(This is the generated type, the original was nested with multiple types and ReturnType.) ActionTypes is a string enum.
const postReducer = (state = initialPostState, action: Actions): PostState => {
const { type, payload } = action;
switch (action.type) {
case ActionTypes.LOAD_POST_SUCCESS: {
const { post } = action.payload; // No error
return { ...state, loading: false, success: true, post };
}
}
switch (type) {
case ActionTypes.LOAD_POST: {
return { ...state, loading: true };
}
case ActionTypes.LOAD_POST_SUCCESS: {
// [ts] Type 'string | { url: string; post: IFullPost; }' has no property 'post' and no string index signature.
const { post } = payload;
return { ...state, loading: false, success: true, post };
}
case ActionTypes.LOAD_POST_FAIL: {
return { ...state, loading: false, success: false, post: null };
}
default:
return state;
}
};
Why does the first one work but not the second one?
Upvotes: 3
Views: 1806
Reputation: 3292
You are experiencing TypeScript reaching the limits of its type inference by type guards.
In your non-working example, TypeScript can not infer anything about the already destructured variable payload
, although it would be technically doable.
I guess type guards only work on objects directly/literally involved in the guarding expression.
Upvotes: 0
Reputation: 3179
It's by design. Here is a very simplified example:
type Actions =
{
type: 1,
payload: string;
} |
{
type: 2,
payload: { a: string }
}
function r(action: Actions) {
const { type } = action;
switch (type) {
case 2: {
// Type 'string | { a: string; }' has no property 'a' and no string index signature.
const { a } = action.payload;
}
}
}
When we destruct action
object: const { type, payload } = action;
we lose coupling information of destructed types. After this, type
constant will have 1 | 2
type and payload
will have string | { a: string; }
, i.e. each type will union all possible options based on Actions
type. This is why TS cannot figure out the exact type of payload
, because in the switch
condition we have absolutely separate variable.
Upvotes: 3
Reputation: 276115
You have to switch on action.type
in order for the action.payload
to change its type in the case statements.
Upvotes: 0