Home > Software engineering >  TypeScript types for passing a React component to React.cloneElement
TypeScript types for passing a React component to React.cloneElement

Time:02-26

I'm converting a React component from JavaScript to TypeScript. The component includes a call to React.cloneElement, passing in a value that it receives via component props. The value in question will be a different React component, referred to as Dep in the example below. Dep is a class-based component, which is why it works as both a type and a value). I'm getting a type error that I'm not sure how to resolve.

Here is a simplified example with only what is necessary to demonstrate the error:

import React from "react";
import Dep from "./Dep";

export interface ExampleProps {
  dep: Dep;
}

export const Example = (props: ExampleProps) => {
  let dep = React.cloneElement(props.dep);
                            // ^~~~~~~~~ TS error here
  return (
    <div>
      {dep}
    </div>
  );
};

And the error from the compiler:

src/components/Example.tsx:9:32 - error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'Dep' is not assignable to parameter of type 'ReactElement<unknown, string | JSXElementConstructor<any>>'.
      Type 'Dep' is missing the following properties from type 'ReactElement<unknown, string | JSXElementConstructor<any>>': type, key

9   let dep = React.cloneElement(props.dep);
                                 ~~~~~~~~~

  node_modules/@types/react/index.d.ts:331:14
    331     function cloneElement<P>(
                     ~~~~~~~~~~~~
    The last overload is declared here.


Found 1 error.

I see that cloneElement expects its first argument to be ReactElement. It's not clear to me what this type represents and why Dep is not compatible. Here is the definition of ReactElement, for reference:

interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}

The error message says that the reason it's not compatible is because the type and key properties are missing, but it's unclear why it expects such properties to be there, and how I can represent my component with this shape.

CodePudding user response:

I see that cloneElement expects its first argument to be ReactElement. It's not clear to me what this type represents and why Dep is not compatible.

An Element is typically made by JSX syntax. For example, <div />, <Router /> and <Dep /> are all examples of elements. Note that Dep is not an element, it's a component. <Dep /> with the JSX angle brackets is an element. Elements can also be created by manually calling React.createElement, though that's uncommon.

So if you're planning to use Example like this:

<Example dep={<Dep />} />

Then you'll want to make the type be:

export interface ExampleProps {
  dep: React.ReactElement;
}

Note that this will allow any elements, not just ones that reference the Dep component.

CodePudding user response:

The issue is that Dep is not a Typescript type, but a React component (JS function in this case).

If you want to accept only components with the same signature (same props) as Dep, just do this:

export interface ExampleProps {
  dep: typeof Dep;
}

Otherwise, if you want to accept any kind of components, you can do:

export interface ExampleProps {
  dep: React.ReactElement;
}
  • Related