skip to Main Content

Let’s say I have an object of type:

type BulkItem = { id: number; type: 'bulk'; tonnage: number }
type RegularItem = { id: number; type: 'regular'; weight: number }
export type CartItem = BulkItem | RegularItem // there could be more item types here

Now, in my component, let’s say I get a list of CartItems that I want to map across and display things.

const items = getCartItems(...)

<div>
    {items.map(item => {
        switch(item.type) {
            case 'regular': return (<div>Weight: {item.weight}</div>)
            case 'bulk': return (<div>Tonnage: {item.tonnage}</div>)
        }
    })}
</div>

Now, I get a TS error saying that item.weight does not apply to CartItem because bulk items don’t have a weight property. Likewise, item.tonnage does not apply to CartItem because regular items don’t have a tonnage property.

I know this is kind of a contrived example but I’m basically asking if there’s a way to coerce each item in a list to be of a type that I would expect it to be in each case of the switch statement?

2

Answers


  1. Actually, there is no typescript bug in your code, unless the getCartItems returns CartItem[].

    If the typescript still gives error, you can use Type Guard Functions to identify a CartItem is a BulkItem or RegularItem.

    const isRegularItem = (item: CartItem): item is RegularItem => item.type === 'regular';
    const isBulkItem = (item: CartItem): item is BulkItem => item.type === 'bulk';
    

    Then, employ type narrowing with if statements:

    <div>
      {items.map(item => {
        if (isRegularItem(item)) {
          return <div key={item.id}>Weight: {item.weight}</div>;
        }
        if (isBulkItem(item)) {
          return <div key={item.id}>Tonnage: {item.tonnage}</div>;
        }
        return null;
        })}
    </div>
    
    Login or Signup to reply.
  2. I’m basically asking if there’s a way to coerce each item in a list
    to be of a type that I would expect it to be in each case of the
    switch statement?

    There’s 2 different types that you could be referring to here.
    Your switch statement is checking on a string property named 'type', and not necessarily the typeScript 'type'.

    • If you want to use the switch statement for the string 'type'
      You can restructure your TS types to just be one model i.e.

      type CartItem = { 
            id: number; 
            type: 'bulk' | 'regular'; // You can extend this when needed
            tonnage?: number; 
            weight?: number  
      }
      

    Then your switch statement could stay the same, if you want.

    • If you want to keep your TS types as-is
      You can change your switch statement to check the actual TS ‘type’

       const items:BulkItem | RegularItem []  = getCartItems(...)
      
        ...switch(typeOf item) {
            case 'BulkItem': return (<div>Weight: {item.weight}</div>)
            case 'RegularItem': return (<div>Tonnage: {item.tonnage}</div>)
        }
      
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search