Typescript: Create a React Component with conditional props for each variant

Instead of making a prop optional, we can specify different variants for a component and ensure type safety

ยท

2 min read

You have a lovely component that takes some props.

interface PropsA {
  pear: string;
  apple: string;
}

const ComponentA: FC<PropsA> = (props) => {
  return ///;
};

We then realise that we have another component which looks the same but has a different prop that is essential to this second component, and not needed in the first.

interface PropsB {
  pear: string;
  apple: string;
  plum: string; // essential for ComponentB
}

const ComponentB: FC<PropsB> = (props) => {
  return /// identical looking component to componentA;
};

Now we have 2 components to maintain, even though they share 2 of the same props, and have all the same styles etc.

You could just make it into one component and make the extra prop conditional plum?:string and then check for the extra prop in the component:

interface PropsAB {
  pear: string;
  apple: string;
  plum?: string;
}

const Component: FC<PropsAB> = (props) => {
  if(props.plum){
    ///
  } else {
    ///
  }
};

This approach is ok most of the time, but another developer might not realise that the plum is only needed when the component is of type B, (and is essential), and not required when it is of type A.

interface BaseProps {
  // shared props
  pear: string;
  apple: string;
}

export type Variant = 'a' | 'b'; // label our variants

interface AProps extends BaseProps {
  // give each variant their own interface which extends the shared props with 'variant' prop
  variant: 'a';
}

interface BProps extends BaseProps {
  variant: 'b';
  plum: string; // our extra prop which is required only for this variant
}

export type ComponentProps =  AProps | BProps;


const Component = (props: ComponentProps) => {
   return <div {...props}>Fruit</div>;
};

export default Component;

Now we can use our component, specify the variant, and get helpful typescript errors and auto-complete.

const App = () => {
  return (
    <>
      <Component variant="a" pear="" apple=""  /> // OK
      <Component variant="b" pear="" apple="" /> // Error: Property 'plum' is missing 
    </>
  );
};

export default App;
ย