Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
622 views
in Technique[技术] by (71.8m points)

reactjs - How to rewrite the protected/private route using TypeScript and React-Router 4, 5 or 6?

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

Update (Mar 2021)

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:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...