How to rewrite the protected/private route using TypeScript and React-Router 4 and 5?
Probably the error has to do with the typing and the implicit return in rendering. When you fix this you get ultimately to something like this:
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/login'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
This component can be used like this:
<PrivateRoute
path='/private'
isAuthenticated={this.props.state.session.isAuthenticated}
component={PrivateContainer}
/>
There are a few draw backs with the solution above. One of the is that you lose type safety.
Probably extending the Route
component is the better idea.
import * as React from 'react';
import {Redirect, Route, RouteProps} from 'react-router';
export interface ProtectedRouteProps extends RouteProps {
isAuthenticated: boolean;
authenticationPath: string;
}
export class ProtectedRoute extends Route<ProtectedRouteProps> {
public render() {
let redirectPath: string = '';
if (!this.props.isAuthenticated) {
redirectPath = this.props.authenticationPath;
}
if (redirectPath) {
const renderComponent = () => (<Redirect to={{pathname: redirectPath}}/>);
return <Route {...this.props} component={renderComponent} render={undefined}/>;
} else {
return <Route {...this.props}/>;
}
}
}
So you can use the component like this:
const defaultProtectedRouteProps: ProtectedRouteProps = {
isAuthenticated: this.props.state.session.isAuthenticated,
authenticationPath: '/login',
};
<ProtectedRoute
{...defaultProtectedRouteProps}
exact={true}
path='/'
component={ProtectedContainer}
/>
Update (Nov 2019)
If you prefer to write functional components you can do it in a very similar manner. This also works with React Router 5:
import * as React from 'react';
import { Redirect, Route, RouteProps } from 'react-router';
export interface ProtectedRouteProps extends RouteProps {
isAuthenticated: boolean;
isAllowed: boolean;
restrictedPath: string;
authenticationPath: string;
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = props => {
let redirectPath = '';
if (!props.isAuthenticated) {
redirectPath = props.authenticationPath;
}
if (props.isAuthenticated && !props.isAllowed) {
redirectPath = props.restrictedPath;
}
if (redirectPath) {
const renderComponent = () => <Redirect to={{ pathname: redirectPath }} />;
return <Route {...props} component={renderComponent} render={undefined} />;
} else {
return <Route {...props} />;
}
};
export default ProtectedRoute;
Update (Dec 2019)
If you want to redirect a user to the path the user wanted to access first, you need to remember the path, so you can redirect after successful authentication. The following answer will guide you through that:
Redirecting a user to the page they requested after successful authentication with react-router-dom
You can still use the SFC form, which I find a little cleaner. Just mix in any props you need with the RouteProps
:
const PrivateRoute: React.SFC<RouteProps> = ({
component: Component,
...rest
}: {
component: React.ComponentType<RouteProps>;
}) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated
? <Component {...props} />
: <Redirect to="/login" />
}
/>
);
My PrivateRoute
import React from 'react'
import {Redirect, Route, RouteProps} from 'react-router'
export interface IPrivateRouteProps extends RouteProps {
isAuth: boolean // is authenticate route
redirectPath: string // redirect path if don't authenticate route
}
const PrivateRoute: React.FC<IPrivateRouteProps> = (props) => {
return props.isAuth ? (
<Route {...props} component={props.component} render={undefined} />
) : (
<Redirect to={{pathname: props.redirectPath}} />
)
}
export default PrivateRoute
Using
<PrivateRoute isAuth={false} redirectPath="/login" path="/t1">
<Pages.Profile /> your`s protected page
</PrivateRoute>