Joelgullander
Joelgullander

Reputation: 1684

Redux actions typescript return type not working

So basicly I am trying to return types for a set of functions & being able to match that in my redux reducer.

Here is my attempt:

actions.ts

export const SET_CART_ITEMS = "SET_CART_ITEMS";
export const SET_CART_TOTALS = "SET_CART_TOTALS";

export const ADD_CART_ITEM = "ADD_CART_ITEM";
export const REMOVE_CART_ITEM = "REMOVE_CART_ITEM";

export const SET_CART_LOADING = "SET_CART_LOADING";

export function setCart(cart: Cart) {
  return {
    type: SET_CART_ITEMS,
    cart
  } as const;
}

export function setTotals(totals: CartTotal) {
  return {
    type: SET_CART_TOTALS,
    totals
  }
}

export function addCartItem(cart: Cart) {
  return {
    type: ADD_CART_ITEM,
    cart
  } as const;
}

export function removeCartItem(cart: Cart) {
  return {
    type: REMOVE_CART_ITEM,
    cart
  } as const;
}

export function toggleCartLoading(loading: boolean) {
  return {
    type: SET_CART_LOADING,
    loading
  } as const;
}

export type CartActions = ReturnType<typeof setCart>| ReturnType<typeof addCartItem> | ReturnType<typeof removeCartItem> | ReturnType<typeof toggleCartLoading> | ReturnType<typeof setTotals>

reducer.ts

import { 
   CartActions,  
   ADD_CART_ITEM,
   REMOVE_CART_ITEM,
   SET_CART_ITEMS,
   SET_CART_TOTALS,
   SET_CART_LOADING 
} from '../../../actions';

export default (state: CartState = initialState, action: CartActions) : CartState => {
  switch (action.type) {
    case ADD_CART_ITEM:
      return {
        ...state,
        cart: {
          ...action.cart
        }
      }
    case REMOVE_CART_ITEM:
      return {
        ...state,
        cart: {
          ...action.cart
        }
      }
    case SET_CART_ITEMS:
      return {
        ...state,
        cart: action.cart
      }
    case SET_CART_TOTALS:
      return {
        ...state,
        totals: action.totals
      }
    case SET_CART_LOADING:
      return {
        ...state,
        loading: action.loading
      }
    default:
      break;
  }
  return state;
}

This does not work at all, the error i'm getting is in the reducer switch saying:

"Property 'cart' does not exist on type 'CartActions'.
  Property 'cart' does not exist on type '{ type: string; totals: CartTotal; }'."


Property 'loading' does not exist on type '{ readonly type: "SET_CART_ITEMS"; readonly cart: Record<string, CartItem>; } | { readonly type: "ADD_CART_ITEM"; readonly cart: Record<string, CartItem>; } | { ...; } | { ...; } | { ...; }'.
  Property 'loading' does not exist on type '{ readonly type: "SET_CART_ITEMS"; readonly cart: Record<string, CartItem>; }'.ts(2339)

Am I missing something obvious? I would really want to not needing to type all the actions by interfaces manually.

Inspiration from https://gist.github.com/schettino/c8bf5062ef99993ce32514807ffae849

Upvotes: 3

Views: 2011

Answers (2)

ford04
ford04

Reputation: 74500

It is necessary, that each action creator return type is annotated with as const, so TS can discriminate your actions in the switch statement of the reducer.

In setTotals this const assertion seems to be missing - at least the provided code works again in the playground, after having added it.

You could create a generic action creator to make it more scalable and omit as const:

function createAction<T extends string, U, K extends string>(type: T, key: K, payload: U) {
  return ({
    type,
    [key]: payload
  }) as { type: T; } & { [P in K]: U }
  // need to cast, so TS doesn't return a widened index signature type
}

export function setTotals(totals: CartTotal) {
  return createAction(SET_CART_TOTALS, "totals", totals)
}
// same with the other functions

Code sample in Playground

Upvotes: 1

Mohsen ZareZardeyni
Mohsen ZareZardeyni

Reputation: 946

With the way you implemented CartActions, you should always assign the type of each action to it.

for example, you should change:

case REMOVE_CART_ITEM:
  return {
    ...state,
    cart: {
      ...action.cart
    }
  }

to this:

case REMOVE_CART_ITEM:
  return {
    ...state,
    cart: {
      ...action.cart
    }
  } as ReturnType<typeof removeCartItem>

or you can modify your CartActions so type act as a separator:

const enum ActionTypes {
  SET_CART_ITEMS,
  SET_CART_TOTALS,
  ADD_CART_ITEM,
  REMOVE_CART_ITEM,
  SET_CART_LOADING
}



type CartActions =
  | {
      type:
        | ActionTypes.SET_CART_ITEMS
        | ActionTypes.ADD_CART_ITEM
        | ActionTypes.REMOVE_CART_ITEM;
      cart: Cart;
    }
  | {
      type: ActionTypes.SET_CART_TOTALS;
      totals: CartTotal;
    }
  | {
      type: ActionTypes.SET_CART_LOADING;
      loading: boolean;
    };

export function setCart(cart: Cart): CartActions {
  return {
    type: ActionTypes.SET_CART_ITEMS,
    cart
  };
}

export function setTotals(totals: CartTotal): CartActions {
  return {
    type: ActionTypes.SET_CART_TOTALS,
    totals
  };
}

export function addCartItem(cart: Cart): CartActions {
  return {
    type: ActionTypes.ADD_CART_ITEM,
    cart
  };
}

export function removeCartItem(cart: Cart): CartActions {
  return {
    type: ActionTypes.REMOVE_CART_ITEM,
    cart
  };
}

export function toggleCartLoading(loading: boolean): CartActions {
  return {
    type: ActionTypes.SET_CART_LOADING,
    loading
  };
}

Upvotes: 1

Related Questions