Search code examples
javascriptreactjstypescriptreact-typescript

TypeScript not seeing defined types using a type that references multiple types


I have a type defined that looks like this:

export type MediaProps = ImageMediaProps | OembedProps;

Then have the types it's referencing defined as the following above that code:

type SharedMediaProps = {
  /** Media type */
  type: "image" | "oembed";
  /** Media source URL */
  src: string;
};

type ImageMediaProps = SharedMediaProps & {
  /** Image alternate text */
  alt: string;
  /** Image width */
  width: number;
  /** Image height */
  height: number;
};

type OembedProps = SharedMediaProps & {
  /** Enable video autoplay */
  autoplay?: boolean;
  /** Enable video loop */
  loop?: boolean;
  /** Allow fullscreen */
  allowFullscreen?: boolean;
  /** Allow picture-in-picture */
  allowPictureInPicture?: boolean;
  /** oEmbed title */
  title?: string;
};

Then in my React component, I have:

export function Media({
  type,
  title,
  width,
  height,
  src,
  autoplay = false,
  loop = false,
  allowFullscreen = true,
  allowPictureInPicture = true,
}: MediaProps) {

but I'm getting notices saying title, width, height, autoplay, loop, allowFullscreen, allowPictureInPicture aren't defined.

For example, the specific notice I'm getting is:

Property 'allowFullscreen' does not exist on type 'MediaProps'.ts(2339)

It's also happening on other components I've created.


Solution

  • First, move the type property into the not shared props, so it can be used to discrimitate the type:

    type SharedMediaProps = {
       /** Media source URL */
       src: string;
    };
    
    type ImageMediaProps = SharedMediaProps & {
       /** Media type */
       type: "image";
       /** Image alternate text */
       alt: string;
       /** Image width */
       width: number;
       /** Image height */
       height: number;
    };
    
    type OembedProps = SharedMediaProps & {
       /** Media type */
       type: "oembed";
       /** Enable video autoplay */
       autoplay?: boolean;
       /** Enable video loop */
       loop?: boolean;
       /** Allow fullscreen */
       allowFullscreen?: boolean;
       /** Allow picture-in-picture */
       allowPictureInPicture?: boolean;
       /** oEmbed title */
       title?: string;
    };
    

    Then define MediaProps as union of those types:

    type MediaProps = ImageMediaProps | OembedProps;
    

    After all this you still can't deconstruct the property in the paramenter because you have to discriminate the type first:

    export function Media(props: MediaProps) {
       if(props.type === "image") {
          const {alt, width, height} = props;
       } else {
          const {autoplay, loop, allowFullscreen, allowPictureInPicture, title} = props;
       }
    }
    

    Demo: https://tsplay.dev/NBkVnm