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 typeany
. - 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 officialreact-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;