0

those are the actions.

export const fetchProductsStart = () => ({
  type: ProductListActionTypes.PRODUCT_LIST_START,
});

export const fetchProductSuccess = (products: IProduct) => ({
  type: ProductListActionTypes.PRODUCT_LIST_SUCCESS,
  payload: products,
});

export const fetchProductFailure = (error: any) => ({
  type: ProductListActionTypes.PRODUCT_LIST_FAILURE,
  payload: error,
});

I wrote Action like this:

export type Action =
  | ReturnType<typeof fetchProductsStart>
  | ReturnType<typeof fetchProductSuccess>
  | ReturnType<typeof fetchProductFailure>;

I passed this to the reducer's Action type

 case ProductListActionTypes.PRODUCT_LIST_SUCCESS:
      return { loading: false, products: action.payload };
    case ProductListActionTypes.PRODUCT_LIST_FAILURE:
      return { loading: false, error: action.payload };

I got warning for action.payload. it says "Property 'payload' does not exist on type Action"

2 Answers 2

1

Not every branch in the Action union has a payload key, and the compiler does not know which branch to take, since the type of the action is widened to just string instead of 'PRODUCT_LIST_START' | 'PRODUCT_LIST_SUCCESS' | 'PRODUCT_LIST_FAILURE'

If you look at the type of Action it is

type Action = {
    type: string;
} | {
    type: string;
    payload: IProduct;
} | {
    type: string;
    payload: any;
}

This is because ProductListActionTypes is inferred as

const ProductListActionTypes: {
    PRODUCT_LIST_START: string;
    PRODUCT_LIST_SUCCESS: string;
    PRODUCT_LIST_FAILURE: string;
}

The compiler can then not determine which branch of the Action union you want, it cannot discriminate which action type it is, because the type has been widened to string.

You can fix this by adding as const to the end of the ProductListActionTypes declaration.

export const ProductListActionTypes = {
  PRODUCT_LIST_START: 'PRODUCT_LIST_START',
  PRODUCT_LIST_SUCCESS: 'PRODUCT_LIST_SUCCESS',
  PRODUCT_LIST_FAILURE: 'PRODUCT_LIST_FAILURE'
} as const;

and then the type of ProductListActionTypes is inferred as

const ProductListActionTypes: {
  readonly PRODUCT_LIST_START: "PRODUCT_LIST_START";
  readonly PRODUCT_LIST_SUCCESS: "PRODUCT_LIST_SUCCESS";
  readonly PRODUCT_LIST_FAILURE: "PRODUCT_LIST_FAILURE";
}

and then Action is

type Action = {
    type: "PRODUCT_LIST_START";
} | {
    type: "PRODUCT_LIST_SUCCESS";
    payload: IProduct;
} | {
    type: "PRODUCT_LIST_FAILURE";
    payload: any;
}

And now the compiler knows which branch of the Action union to take when you give it one of the action types.

Sign up to request clarification or add additional context in comments.

Comments

1

It seems like you want to utilize Discriminated Union. But the Typescript assumes the action to be of type:

type Action = {
    type: string;
} | {
    type: string;
    payload: any;
} | {
    type: string;
    payload: any;
}

And, thus it cannot distinguish types in switch statement. What would be helpful here if we can show that type is going to be 'PRODUCT_LIST_START' | 'PRODUCT_LIST_SUCCESS' | 'PRODUCT_LIST_FAILURE'. If we can tell Typescript that our ProductListActionTypes is going to be unchanging i.e. we need to const assert this object.

We can do constant assertion like this:

const ProductListActionTypes = {
  PRODUCT_LIST_START: "PRODUCT_LIST_START",
  PRODUCT_LIST_SUCCESS: "PRODUCT_LIST_SUCCESS",
  PRODUCT_LIST_FAILURE: "PRODUCT_LIST_FAILURE",
} as const;

And now our Action type looks like this:

type Action = {
    type: "PRODUCT_LIST_START";
} | {
    type: "PRODUCT_LIST_SUCCESS";
    payload: any;
} | {
    type: "PRODUCT_LIST_FAILURE";
    payload: any;
}

And, you'd not see the wavy lines.

See full code at Typescript Playground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.