I was trying to create a <PrivateRoute>
as describe in the react-router documents using TypeScript. Can anyone help me out?
The privateRoute in react-router document:
const PrivateRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={props => ( fakeAuth.isAuthenticated ? ( <Component {...props}/> ) : ( <Redirect to={{pathname: '/login', state: { from: props.location } }}/> ) )}/> )
Below is my TypeScript version(it won't work) :
const PrivateRoute = (theProps: { path: string, component: React.SFC<RouteComponentProps<any> | undefined> | React.ComponentClass<RouteComponentProps<any> | undefined> }) => { return <Route path={theProps.path} render={props => ( fakeAuth.isAuthenticated ? ( <React.Component {...theProps} /> <!-- **** It will raise error *** --> ) : ( <Redirect to={{ pathname: '/', state: { from: props.location } }} /> ) )} /> }
The <React.Component {...thisProps} />
is not right. The error is: NodeInvocationException: inst.render is not a function TypeError: inst.render is not a function
Both BrowserRouter and HashRouter components were introduced in React Router ver. 4 as subclasses of Router class. Simply, BrowserRouter syncs the UI with the current URL in your browser, This is done by the means of HTML-5 History API. On the other hand, HashRouter uses the Hash part of your URL to sync.
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} />
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;
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
The solution above is a bit outdated. The ProtectedRoute component can simply be written as follows:
import { Redirect, Route, RouteProps } from 'react-router'; export type ProtectedRouteProps = { isAuthenticated: boolean; authenticationPath: string; } & RouteProps; export default function ProtectedRoute({isAuthenticated, authenticationPath, ...routeProps}: ProtectedRouteProps) { if(isAuthenticated) { return <Route {...routeProps} />; } else { return <Redirect to={{ pathname: authenticationPath }} />; } };
If you use React Router V6 you need to replace Redirect
with Navigate
. A full example with redirection to the originally requested page can be found here:
As children of <Routes>
need to be <Route>
elements the <ProtectedRoute>
can be changed to:
export type ProtectedRouteProps = { isAuthenticated: boolean; authenticationPath: string; outlet: JSX.Element; }; export default function ProtectedRoute({isAuthenticated, authenticationPath, outlet}: ProtectedRouteProps) { if(isAuthenticated) { return outlet; } else { return <Navigate to={{ pathname: authenticationPath }} />; } };
<ProtectedRoute>
can now be applied like follows:
const defaultProtectedRouteProps: Omit<ProtectedRouteProps, 'outlet'> = { isAuthenticated: !!sessionContext.isAuthenticated, authenticationPath: '/login', }; return ( <div> <Routes> <Route path='/' element={<Homepage />} /> <Route path='dashboard' element={<ProtectedRoute {...defaultProtectedRouteProps} outlet={<Dashboard />} />} /> <Route path='protected' element={<ProtectedRoute {...defaultProtectedRouteProps} outlet={<Protected />} />} /> <Route path='nested' element={<ProtectedRoute {...defaultProtectedRouteProps} outlet={<Layout />} />}> <Route path='one' element={<Protected />} /> <Route path='two' element={<Protected />} /> </Route> <Route path='login' element={<Login />} /> </Routes> </div> );
I've also updated the React Router 6 example. By now there is even an official guide about this: https://reactrouter.com/docs/en/v6/examples/auth
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" /> } /> );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With