Search code examples
reactjstypescript

Discriminated union props type not reporting error when passing discriminator dynamically


I was using following approach for passing props conditionally (when one prop depends on another prop) in react:

type CommonProps = { age: number };

type ConditionalProps =
  | {
      type: 'view' | 'add';
    }
  | {
      type: 'edit';
      initialData: number;
    };

let Test = (props: CommonProps & ConditionalProps) => {
  return <div />;
};

My goal was:

  • if type is passed as "view" or "add", then I want TS to error if I pass initialData
  • if type is passed as "edit" then initialData should be required.

This actually worked most of the time, until value of type was dynamic. When I passed value of type like this:

 <Test
   age={9}
   type={Math.random() > 0.5 ? 'edit' : 'view'}
   initialData={9}
 />

Now it seems there is bug above, because TS isn't complaining anymore, and it can happen that type is "view" and I also pass initialData, which violates my first goal above.

What is the solution in such case? I suppose TS should report an error? If yes, how?


Solution

  • In general, Typescript ignores excess properties unless they're explicitly assigned or passed as an argument.

    So, although in your example here you're passing { age: number, type: "view", initialData: number }, that satisfies the type { type: "view" | "add" }. This is similar to the way that, for instance, you might ignore irrelevant fields in data from an external source.

    If however you really want to ban the extra prop in this instance, you can do so by specifying that its type should be never:

    type ConditionalProps =
      | {
          type: 'view' | 'add';
          initialData?: never;
        }
      | {
          type: 'edit';
          initialData: number;
        };
    

    Note that we specify initialData as never and also optional, so that it may (must) be omitted.

    With the never type in place, Typescript will report an error unless the type prop is "edit".

    Minimal repro available here