Union of types was reduced to 'never' breaks typing

I have a collection of types all extended from a base interface:

interface BaseType {
  id: number;
  titleText: string;
}

export enum ComponentType {
  AUDIO = 'type_audio',
  ARTICLE = 'type_article',
  BASIC = 'type_basic',
  DOWNLOAD = 'type_download',
  LINK = 'type_link',
  QUOTE = 'type_quote',
  VIDEO = 'type_video',
}

export interface BasicComponent extends BaseType {
  type: ComponentType.BASIC;
}

export interface QuoteComponent extends BaseType {
  type: ComponentType.QUOTE;
  subtitle?: string;
}

export interface LinkComponent extends BaseType {
  type: ComponentType.LINK | ComponentType.ARTICLE;
  subtitle?: string;
}


export interface DownloadComponent extends BaseType {
  type: ComponentType.DOWNLOAD;
}

export interface AudioComponent extends BaseType {
  type: ComponentType.AUDIO;
  mediaType: MediaType.AUDIO;
  mediaRef: Reference;
  configRef: Reference;
}

export interface VideoComponent extends BaseType {
  type: ComponentType.VIDEO;
  mediaType: MediaType.VIDEO;
  mediaRef: Reference;
  configRef: Reference;
}

But when I create a type that can be any one of these component types, and use a switch case to assign them to a React component, I get an error.

export type Component =
  | BasicComponent
  | AudioComponent
  | DownloadComponent
  | LinkComponent
  | VideoComponent
  | QuoteComponent;

  const getChild = () => {
    switch (props.type) {
      case ComponentType.AUDIO:
        return AudioComponent;
      case ComponentType.DOWNLOAD:
        return DownloadComponent;
      case ComponentType.ARTICLE:
      case ComponentType.LINK:
        return LinkComponent;
      case ComponentType.QUOTE:
        return QuoteComponent;
      case ComponentType.VIDEO:
        return VideoComponent;
      case ComponentType.BASIC:
        return BasicComponent;
    }
  };

  const Component = getChild();

  return <Component {...props} />;
  //           ^^^
  // The intersection 'IntrinsicAttributes & AudioTeaser & { children?: ReactNode; } &
  // DownloadTeaser & LinkTeaser & QuoteTeaser & VideoTeaser & BasicTeaser' was reduced
  // to 'never' because property 'type' has conflicting types in some constituents.

  // Type props is not assignable to type 'never'.

1 answer

  • answered 2021-06-23 12:12 Titian Cernicova-Dragomir

    This would require something like corelated type, where typescript could know that the type of Component and props are corelated. At the moment this does not exist.

    What happens is that Component ends up being a union of all possible components. Since Component can be any one of the valid components, it is only safe to create this component with props that satisfy all possible component props (so an intersection of all possible props). Since the props are mutually exclusive, you end up with a never (ie type that has no possible value) somewhere in this intersection.

    The simplest solution is to use a type assertion to convert the union of components to a component that accepts a props that is a union of all possible props. Now this is not generally safe, but in this case we are telling typescript we know better:

        const Component = getChild() as React.FC<Component>;
    
        return <Component {...props} />;
    

    Playground Link