score:0

Accepted answer

Here is a working version.

Props needs to extend PropsWithChildren<any>, like so:

import * as React from "react";
import { render } from "@testing-library/react";

function renderContainer<Props extends React.PropsWithChildren<any>>(
  //function renderContainer<Props>(
  Container: React.ComponentType<Props>,
  props?: Props
) {
  return render(
    <Container {...props}>
      <div>Child</div>
    </Container>
  );
}

type CustomComponentProps = {
  children?: React.ReactNode;
  prop1: number;
  prop2?: string;
};
const CustomComponent = ({ prop1, prop2, children }: CustomComponentProps) => (
  <div>{children}</div>
);

// Expected behavior:
renderContainer(CustomComponent, { prop1: 1 }); // ok
renderContainer(CustomComponent, { prop1: 1, prop2: "string" }); // ok
renderContainer(CustomComponent, { prop3: 3 }); // error

/* Argument of type '({ prop1, prop2, children }: CustomComponentProps) => JSX.Element' is not assignable to parameter of type 'ComponentType<{ prop3: number; }>'.
  Type '({ prop1, prop2, children }: CustomComponentProps) => JSX.Element' is not assignable to type 'FunctionComponent<{ prop3: number; }>'.
    Types of parameters '__0' and 'props' are incompatible.
      Property 'prop1' is missing in type 'PropsWithChildren<{ prop3: number; }>' but required in type 'CustomComponentProps'.ts(2345) /*


If you want the error on the props instead of CustomComponent:

import * as React from "react";
import { render } from "@testing-library/react";

function renderContainer<Props extends React.PropsWithChildren<any>>(
  //function renderContainer<Props>(
  Container: React.ComponentType<Props>,
  props?: Props
) {
  return render(
    <Container {...props}>
      <div>Child</div>
    </Container>
  );
}

type CustomComponentProps = {
  children?: React.ReactNode;
  prop1: number;
  prop2?: string;
};
const CustomComponent: React.ComponentType<CustomComponentProps> = ({ prop1, prop2, children }) => (
  <div>{children}</div>
);

// Expected behavior:
renderContainer(CustomComponent, { prop1: 1 }); // ok
renderContainer(CustomComponent, { prop1: 1, prop2: "string" }); // ok
renderContainer(CustomComponent, { prop3: 3 }); // error

/* Argument of type '{ prop3: number; }' is not assignable to parameter of type 'CustomComponentProps'.
Object literal may only specify known properties, but 'prop3' does not exist in type 'CustomComponentProps'. Did you mean to write 'prop1'? /*


codesandbox

score:1

Actually, the easier way to resolve this problem is to think the other way around, and setting the generic in renderContainer as the Component type it self, instead of the props.

type ReactComponent = JSXElementConstructor<any>;

type CustomComponentProps = {
  children?: React.ReactNode;
  prop1: number;
  prop2?: string;
};

const CustomComponent = ({ prop1, prop2, children }: CustomComponentProps) => (
  <div>{children}</div>
);


declare function renderContainer<T extends ReactComponent>(container: T,  props: React.ComponentProps<T>): React.ReactNode
renderContainer(CustomComponent, { prop1: 1 }); // ok
renderContainer(CustomComponent, { prop1: 1, prop2: "string" }); // ok
renderContainer(CustomComponent, { prop3: 3 }); // error

This way is more natural, because you are defining what will be the component first and then the props will be inferred by TypeScript, you don't even have to specifically pass any generic into the function.


Related Query

More Query from same tag