Search code examples
reactjstypescriptreact-propsconditional-types

define a prop type as a single number or array of numbers based on another prop


Here is a simple example of what I am looking for. I am implementing an array and I want to check if the multi prop is true, type of items should be number[]. Otherwise, set the type to number.

interface EnhancedSelectProps {
  items: multi ? number[] : number;
  multi?: boolean;
}

const EnhancedSelect = ({
  items,
  multi,
}: EnhancedSelectProps) => {}

I have tried the union type but it is not working. when I want to do something like this, it will give me a ts error.

interface EnhancedSelectProps {
  items?: number[] | number;
  multi?: boolean;
}

const EnhancedSelect = ({
  items,
  multi,
}: EnhancedSelectProps) => {
  if(multi) console.log(items.length);
    else console.log(items)
}

Solution

  • The simplest option is to use a discriminated union, discriminated on the multi field:

    type EnhancedSelectProps = {
      items?: number[];
      multi: true;
    } | {
      multi: false; 
      items?: number;
    }
    
    const EnhancedSelect = ({
      items,
      multi,
    }: EnhancedSelectProps) => {
      if(multi) console.log(items?.length);
        else console.log(items)
    }
    EnhancedSelect({ multi: true, items: [1,2]})
    EnhancedSelect({ multi: true, items: 1}) // error
    EnhancedSelect({ multi: false, items: [1,2]}) // error
    EnhancedSelect({ multi: false, items: 1}) 
    EnhancedSelect({ items: [1,2]}) // error
    EnhancedSelect({ items: 1 }) 
    

    Playground Link

    If you are using an older version of Typescript (<= 4.6), you can't destructure the parameter and still have TS understand the relationship between the fields, you will need to wither use the parameter itself or destructure after the check:

    
    const EnhancedSelect = (p: EnhancedSelectProps) => {
      if(p.multi) {
        console.log(p.items?.length);
        const { items } = p
        console.log(items?.length);
      } else {
        console.log(p.items)
        const { items } = p
        console.log(items)
      }
    }
    

    Playground Link