How to allow a Component to take a parameter that is either a FunctionComponent or a Component

Viewed 528

I'm using Typescript and ReactJS, and I'm a noob. I'm trying to do something that I think should be simple, but Typescript is getting in the way.

I've got two React page components defined like:

const HomePage: React.FunctionComponent = () => (
  // stateless component, just JSX and no logic
)


class ReceiptPage extends React.Component<IReceiptPageProps> {
  // has logic and state, defines render()
}

And another component that should be able to take both of these as a parameter:

export interface IPublicRouteWrapperProps extends IPublicRouteProps {
  component: typeof Component;   // <---- Works for ReceiptPage, but not HomePage
  layout: typeof Layout;
}

const PublicRouteWrapper: React.FunctionComponent<IPublicRouteWrapperProps> = ({
  component: Component,
  layout: Layout,
  ...rest
}) => (
  <PublicRoute
    {...rest}
    render={props => (
      <Layout>
        <Component {...props} />
      </Layout>
    )}
  />
);

This works when I pass in the ReceiptPage component, but not when I pass in the HomePage component:

    <PublicRouteWrapper
      title="blah"
      path={`${match.url}`}
      component={HomePage} // <-- error here, but works fine with ReceiptPage
      exact={true}
    />

The error is:

Type 'FunctionComponent<{}>' is not assignable to type 'typeof Component'.
Type 'FunctionComponent<{}>' provides no match for the signature 'new <P = {}, S = {}, SS = any>(props: Readonly<P>): Component<P, S, SS>'.

And that makes sense, because HomePage is not of that type.

But I can't figure out what it should be. Is there a way that I can define PublicRouteWrapper so that either page works as a parameter?

1 Answers

I believe you are looking for React.ComponentType, which is a union of ComponentClass (an interface for representing class components) and FunctionComponent. The type can be parameterized with the props type, but you can choose any to allow all components with any props. At a later moment you could increase type safety by specifying the actual type.

The IPublicRouteWrapperProps interface can be written as:

export interface IPublicRouteWrapperProps extends IPublicRouteProps {
  component: React.ComponentType<any>;   // <----  Works for both ReceiptPage & HomePage
  layout: typeof Layout;
}
Related