Search code examples
reactjstypescript

How to correctly extend the React components?


First, please don't try to persuade to use the React Hooks API. I need and want the class API, and that's it.

In below React class component, the PartialsFlags is the associative array-like object which depends on specific inheritor.

import React from "react";


abstract class Gallery<PartialsFlags extends Readonly<{ [key: string]: boolean | undefined; }>>
  extends React.Component<Gallery.Properties<PartialsFlags>>

{

  protected get mustRenderAllPartials(): boolean {
    return Object.values(this.props.partialsFlags).every((value: boolean | undefined): boolean => value === true);
  }

}


namespace Gallery {

  export type Properties<PartialsFlags extends Readonly<{ [key: string]: boolean | undefined; }>> = Readonly<{
    mustVisuallyHideTopHeading: boolean;
    mustVisuallyHideAllHeadings: boolean;
    partialsFlags: PartialsFlags;
  }>;

}

Below component extends the Gallery:

class AdmonitionBlockGallery extends Gallery<AdmonitionBlockGallery.PartialsFlags> {

  public render(): React.ReactNode {
    return <></>;
  }

}


namespace AdmonitionBlockGallery {

  export type PartialsFlags = Readonly<{
    minimal: boolean;
    titles: boolean;
    // ...etc.
  }>;

}

Now if I'll try to use the AdmonitionBlockGallery component:

import AdmonitionBlockGallery from "./_AdmonitionBlockGallery";
import React from "react";
import { createRoot } from "react-dom/client";
import { getExpectedToBeSingleDOM_Element } from "@yamato-daiwa/es-extensions-browserjs";
import { isNonEmptyString } from "@yamato-daiwa/es-extensions";


const applicationRootElement: HTMLElement = getExpectedToBeSingleDOM_Element({
  selector: "#APPLICATION",
  expectedDOM_ElementSubtype: HTMLElement
});

const hasPartialNameBeenSpecified: boolean = isNonEmptyString(applicationRootElement.dataset.partial_name);

createRoot(applicationRootElement).render(
  <AdmonitionBlockGallery
    mustVisuallyHideTopHeading={ applicationRootElement.dataset.must_visually_hide_top_heading === "" }
    mustVisuallyHideAllHeadings={ applicationRootElement.dataset.must_visually_hide_all_headings === "" }
    partialsFlags={
      {
        minimal:
          !hasPartialNameBeenSpecified || applicationRootElement.dataset.partial_name === "MINIMAL",
          titles:
              !hasPartialNameBeenSpecified || applicationRootElement.dataset.partial_name === "TITLES",
          // ...
      }
    }
  />
);

... I'll get the pretty complicated TypeScript error:

TS2786: AdmonitionBlockGallery cannot be used as a JSX component.

  Its type typeof AdmonitionBlockGallery is not a valid JSX element type.

    Type 'typeof AdmonitionBlockGallery' is not assignable to type 'new (props: any, deprecatedLegacyContext?: any) => Component<any, any, any>'.

      Construct signature return types AdmonitionBlockGallery and Component<any, any, any> are incompatible.

        The types returned by render() are incompatible between these types.

           Type React.ReactNode is not assignable to type
import("D:/***Frontend/FrameworksIntegrations/React/Package/node_modules/@types/react/index").ReactNode

             Type

               ReactElement<any, string | JSXElementConstructor<any>>

                 is not assignable to type ReactNode

                   Property children is missing in type

                     ReactElement<any, string | JSXElementConstructor<any>>

                        but required in type ReactPortal

enter image description here

From the viewpoint of JavaScript/JSX, above components are completely valid, and although I have omitted the return value in render method of AdmonitionBlockGallery class, I have checked that components has been rendered correctly.


Solution

  • The TS2786: AdmonitionBlockGallery cannot be used as a JSX component error has disappeared once I have updated

    "@types/react": "18.0.26"
    "@types/react-dom": "18.0.10"
    

    to

    "@types/react": "18.3.5"
    "@types/react-dom": "18.3.0"