Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript cannot destruct union type

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?

like image 486
Joshua Avatar asked Aug 26 '26 09:08

Joshua


1 Answers

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.

like image 148
shohrukh Avatar answered Aug 29 '26 15:08

shohrukh



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!