Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infer the forwarded ref type of third party component in cusumer side component

I had read this question Generics error with forwardRef: Property 'ref' does not exist on type 'IntrinsicAttributes'

Here is my case:

import React from "react";

interface ThirdPartyComponentProps
  extends React.ComponentPropsWithRef<"input"> {}

const ThirdPartyComponent: React.ComponentType<ThirdPartyComponentProps> =
  React.forwardRef<HTMLInputElement, ThirdPartyComponentProps>(
    (props: ThirdPartyComponentProps, ref) => {
      return (
        <div>
          <input ref={ref} type="text" {...props} />
        </div>
      );
    }
  );

interface AppProps
  extends React.ComponentPropsWithRef<typeof ThirdPartyComponent> {}

export const App = React.forwardRef((props: AppProps, ref) => {
  return <ThirdPartyComponent ref={ref} {...props} />;
});

Got TS type error:

Type '((instance: unknown) => void) | MutableRefObject<unknown> | null' is not assignable to type '((instance: HTMLInputElement | null) => void) | RefObject<HTMLInputElement> | null | undefined'.
  Type 'MutableRefObject<unknown>' is not assignable to type '((instance: HTMLInputElement | null) => void) | RefObject<HTMLInputElement> | null | undefined'.
    Type 'MutableRefObject<unknown>' is not assignable to type 'RefObject<HTMLInputElement>'.
      Types of property 'current' are incompatible.
        Type 'unknown' is not assignable to type 'HTMLInputElement | null'.
          Type 'unknown' is not assignable to type 'HTMLInputElement'.ts(2322)
index.d.ts(816, 46): The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & ThirdPartyComponentProps & { children?: ReactNode; }'

Since the ThirdPartyComponent component is a component of a third-party library, I don't know which HTML element the forward ref is used on. It may be the root element or an internal element, such as the input element in the example.

I know that the type of forwarding ref is HTMLInputELement from the error, but this is the type of ref that I learned through an error.

How to declare or infer the type of ThirdPartyComponent ref directly on the consumer side, that is, in the App component.

What's the ref type for React.forwardRef<??, AppProps> generic type.

TypeScript Playground

like image 634
slideshowp2 Avatar asked Oct 15 '22 20:10

slideshowp2


1 Answers

It requires a bit of typescript gymnastic.

Since ThirdPartyComponent is a React.ComponentType<ThirdPartyComponentProps> we should start from inferinf ComponentType.

This is our first line:

T extends React.ComponentType<infer Ref>

Then we need to infer the props of ComponentType, I mean these ThirdPartyComponentProps props.

Ref extends React.DetailedHTMLProps<
      React.InputHTMLAttributes<infer RefElement>,
      any
    >

RefElement is the element we are looking for.

Whole code:

import React from "react";

interface ThirdPartyComponentProps
  extends React.ComponentPropsWithRef<"input"> { }

const ThirdPartyComponent: React.ComponentType<ThirdPartyComponentProps> =
  React.forwardRef<HTMLInputElement, React.ComponentPropsWithRef<"input">>(
    (props: ThirdPartyComponentProps, ref) => {
      return (
        <div>
          <input ref={ref} type="text" {...props} />
        </div>
      );
    }
  );

type InferHTMLElement<T> =
  (T extends React.ComponentType<infer Ref>
    ? (Ref extends React.DetailedHTMLProps<React.InputHTMLAttributes<infer RefElement>, any>
      ? RefElement
      : never
    )
    : never
  )

{
  // HTMLInputElement
  type Test = InferHTMLElement<typeof ThirdPartyComponent>
}

interface AppProps
  extends React.ComponentPropsWithRef<typeof ThirdPartyComponent> { }

export const App = React.forwardRef<InferHTMLElement<typeof ThirdPartyComponent>>((props: AppProps, ref) => {

  return <ThirdPartyComponent ref={ref} {...props} />;
});

Playground

You can try to replace "input" with "div" and you will see that it works.

like image 80
captain-yossarian Avatar answered Oct 23 '22 13:10

captain-yossarian