What type should I declare for object rest props?

Viewed 8300

Here is an example from react-router for how to add a component for protected routes:

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

https://reacttraining.com/react-router/web/example/auth-workflow

I have tried to implement this functionality in my Typescript project, using the example above as inspiration.

import * as React from 'react';
import {
    Route,
    Redirect,
} from 'react-router-dom';

interface PrivateRouteProps {
    component: React.Component; // passed as prop
    isSignedIn: boolean; // injected as prop by redux
    location: Location;
}

const PrivateRoute = (props: PrivateRouteProps) => {
    const { component: Component, isSignedIn, location } = props;

    return (
        <Route
            {...rest}
            render={(routeProps) =>
                isSignedIn ? (
                    <Component {...routeProps} />
                ) : (
                        <Redirect
                            to={{
                                pathname: '/signin',
                                state: { from: location }
                            }}
                        />
                    )
            }
        />
    );
};

export default PrivateRoute;

I get the following errors

[ts] Cannot find name 'rest'.
any

[ts] JSX element type 'Component' does not have any construct or call signatures.
const Component: React.Component<{}, {}, any>
3 Answers

1) You haven't pulled the remaining props from your destructuring operator, to get the rest of the props, you'll need this:

const { component: Component, isSignedIn, location, ...rest } = props;

2) Component isn't what you think it is here, it is an interface for class element construction, but you're using it to define a type. If you're looking to enforce it as an Element, you'll want to use JSX.Element.

Ultimately you should be left with:

import * as React from 'react';
import {
    Route,
    Redirect,
} from 'react-router-dom';

interface PrivateRouteProps {
    component: JSX.Element; // passed as prop
    isSignedIn: boolean; // injected as prop by redux
    location: Location;
}

const PrivateRoute = (props: PrivateRouteProps) => {
    const { component, isSignedIn, location, ...rest } = props;

    return (
        <Route
            {...rest}
            render={(routeProps) =>
                isSignedIn ? (
                    <Component {...routeProps} />
                ) : (
                        <Redirect
                            to={{
                                pathname: '/signin',
                                state: { from: location }
                            }}
                        />
                    )
            }
        />
    );
};

export default PrivateRoute;
import React, { forwardRef, ReactNode } from "react"

interface ICardProps {
    children: ReactNode;
    className?: string;
    [props: string]: any;
}
const Card = forwardRef<HTMLDivElement, ICardProps>(( props, ref ) => {
    const { className, children, ...rest } = props;
    return <div ref={ref} className={className} {...rest}>{children}</div>
})

export default Card;

A simple solution could be:

interface PrivateRouteProps {
  component: React.Component; // passed as prop
  isSignedIn: boolean; // injected as prop by redux
  location: Location;
  // Here prop is a key with type string and value type could be any
  [props: string]: any;
}
Related