21
votes

I'm trying to pass a variable of type React.Component (or React.FunctionComponent) into a Route, like this:

import React from 'react';
import { Route } from 'react-router-dom';

type PrivateRouteProps = {
  component: React.Component | React.FunctionComponent;
  isAuthenticated: boolean;
  login: (...args: any[]) => any;
  path: string;
};

const PrivateRoute: React.FunctionComponent<PrivateRouteProps> = ({
  component: Component,
  isAuthenticated,
  login,
  path,
  ...rest
}) => {
  return (
    <Route
      path={path}
      {...rest}
      render={props => {
        if (isAuthenticated) {
          return <Component {...props} />;
        } else {
          login();
          return null;
        }
      }}
    />
  );
};

But I'm getting this error:

JSX element type 'Component' does not have any construct or call signatures. [2604]

I've read through a bunch of other threads about this issue, but they all seem to deal with this error coming up for a specific component implementation. I can't change the component in question or import it differently (like the accepted answers often suggest), because it could be any component.

I'm using TypeScript 3.1.6, Babel Core 7.1, and React 16.6.3.

6

6 Answers

34
votes

I have encountered this a couple of times. Try these:

  1. Type your PrivateRoute as React.FC<Props>
  2. Type your incoming component as React.ElementType

The ultimate truth about React types comes from the docs

Edit: React.ReactType (deprecated) -> React.ElementType

47
votes

Late to the party, with "@types/react-router-dom": "^4.3.4" and "@types/react": "16.9.1", and if you're using RouteProps, you will probably get the same error.

JSX element type 'Component' does not have any construct or call signatures. [2604]

That's because, in the RouteProps interface, the component is defined as optional, hence it might be undefined.

export interface RouteProps {
  location?: H.Location;
  component?: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
  render?: ((props: RouteComponentProps<any>) => React.ReactNode);
  children?: ((props: RouteChildrenProps<any>) => React.ReactNode) | React.ReactNode;
  path?: string | string[];
  exact?: boolean;
  sensitive?: boolean;
  strict?: boolean;
}

Simply check for if the component is falsy will fix it.

function PrivateRoute({ component: Component, ...rest }: RouteProps) {
  if (!Component) return null;
  return (
    <Route
      {...rest}
      render={props =>
        fakeAuth.isAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: props.location }
            }}
          />
        )
      }
    />
  );
}
13
votes

Even later to the party, but what worked for me is this:

interface PrivateRouteProps extends Omit<RouteProps, "component"> {
  component: React.ElementType;
  // any additional vars
}

PrivateRoute: React.FC<PrivateRouteProps> = ({
  component: Component,
  ...rest
}) => {
// render code
}
2
votes

This is late but in case someone didn't want a solution but an explanation, let's talk about this error using an example to demonstrate it

function PaymentPage(){
  
   return <div>Payment Page</div>
}

say we want to create a dynamic payment form, if the query parameter is with=stripe so we assume he wants to pay with stripe, if it's with razorpay we assume it, ...etc.

we then do something like

function PaymentPage(){
   const router = useRouter;
   const {with_} = router.query;
   let GatewayComponent: Gateway | null = null;
   switch(with_){
     case 'stripe':
       GatewayComponent = <StripeGateway/>;
       break;
     case 'razorpay':
       GatewayComponent = <RazorpayGateway/>;
       break;
   }
   return <GatewayComponent/>
}

Running this, we get

JSX element type 'Component' does not have any construct or call signatures.

What happens?

What are component?

  • Constructors that return elements of type JSX.Element

So?

  • We are not returning a constructor, we are returning a Constructor call, it's the same as assuming that GatewayComponent is a constructor, but it's not, it's a variable holding JSX

So basically, expects x to be a constructor of any type, either a function or a class, if it's a function, the function is the render function, if it's a class, it needs a render method.

Back to our problem

function PaymentPage(){
   const router = useRouter;
   const {with_} = router.query;
   let gateway: Gateway | null = null;
   switch(with_){
     case 'stripe':
       gateway = <StripeGateway/>;
       break;
     case 'razorpay':
       gateway = <RazorpayGateway/>
       break;
   }
   return <React.Fragment> {gateway} </React.Fragment>
}

Because gateway holds JSX, not a constructor that returns JSX

What if you want to use it as a component?

function PaymentPage(){
   const router = useRouter;
   const {with} = router.query;
   let GatewayComponent: Gateway | null = null;
   switch(with_){
     case 'stripe':
       return () => <StripeGateway/>
     case 'razorpay':
       return () => <RazorpayGateway/>
   }
   return <GatewayComponent/>
}

Now it's a constructor, we can use it as a component right now.

Formally, you pass the constructor and not the instance.

1
votes

<PrivateRoute path="'/>
Path gave an error while using interface PrivateRouteProps then i switched it to

export type PrivateRouteProps = {
  component: React.ElementType;
  currentUser?: any;
} & RouteProps;```

Final result below

import { Route, Redirect, RouteProps } from "react-router-dom";
export type PrivateRouteProps = {
  component: React.ElementType;
  currentUser?: any;
} & RouteProps;

const PrivateRoute: React.FC<PrivateRouteProps> = ({
  component: Component,
  currentUser,
  ...rest
}) => (
  <Route
    {...rest}
    render={(props) =>
      currentUser ? <Component {...props} /> : <Redirect to="/" />
    }
  />
);
0
votes

I was able to resolve the error by typing my component as follows:

...
export function withApollo (PageComponent: () => JSX.Element) {
const WithApollo = <T extends object>(props: T) => {
...