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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With