Implement react-router PrivateRoute in Typescript project

The current answers work but I would like to post my solution as I think it has a few advantages:

  • Not overriding the property component with the type any.
  • Leveraging the render method from the library to support both - <Route> Component and children props - without reimplementing already present framework logic/code.
  • Using Recipe: Static Typing from the official react-redux documentation

Example:

import * as React from 'react';
import { connect, ConnectedProps } from 'react-redux';
import {
    Redirect,
    Route,
    RouteProps,
} from 'react-router-dom';
import { AppState } from '../store';

const mapState = (state: AppState) => ({
  loggedIn: state.system.loggedIn,
});

const connector = connect(
  mapState,
  { }
);

type PropsFromRedux = ConnectedProps<typeof connector>;

type Props = PropsFromRedux & RouteProps & {

};

const PrivateRoute: React.FC<Props> = props => {
    const { loggedIn, ...rest } = props;

    return ( !loggedIn ? <Redirect to="/login/" /> :
      <Route {...rest} />
    );
};

export default connector(PrivateRoute);

I found Matt's answer very useful but needed it to work for children as well as component, so tweaked as follows:

import * as React from 'react';
import { Route, Redirect, RouteProps } from 'react-router-dom';
import { fakeAuth } from '../api/Auth';

interface PrivateRouteProps extends RouteProps {
  // tslint:disable-next-line:no-any
  component?: any;
  // tslint:disable-next-line:no-any
  children?: any;
}

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

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

export default PrivateRoute;

Note: This happens to be using fakeAuth like the original training article rather than user1283776's isSignedIn redux stuff, but you get the idea.


The error occurs because PrivateRouteProps has a required property location that isn't provided when you use PrivateRoute in components/Routes.tsx. I assume that this location should instead come from the routeProps that the router automatically passes to the render function of the route, as it did in the original example. Once this is fixed, another error is exposed: components/Routes.tsx is passing a paths property that isn't declared in PrivateRouteProps. Since PrivateRoute is passing any prop it doesn't know about to Route, PrivateRouteProps should extend RouteProps from react-router so that PrivateRoute accepts all props accepted by Route.

Here is components/PrivateRoute.tsx after both fixes:

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

interface PrivateRouteProps extends RouteProps {
    // tslint:disable-next-line:no-any
    component: any;
    isSignedIn: boolean;
}

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

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

export default PrivateRoute;