web-dev-qa-db-fra.com

Comment réécrire la route protégée / privée en utilisant TypeScript et React-Router 4 et 5?

J'essayais de créer un <PrivateRoute> comme décrit dans le react-router documents en utilisant TypeScript. Quelqu'un peut m'aider?

Le privateRoute dans le document react-router:

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route {...rest} render={props => (
    fakeAuth.isAuthenticated ? (
      <Component {...props}/>
    ) : (
      <Redirect to={{pathname: '/login', state: { from: props.location }
   }}/>
  )
 )}/>
)

Ci-dessous ma version TypeScript (cela ne fonctionnera pas):

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 }
                }} />
            )
    )} />
}

Le <React.Component {...thisProps} /> ce n'est pas correct. L'erreur est: NodeInvocationException: inst.render n'est pas une fonction TypeError: inst.render n'est pas une fonction

25
Charlie

L'erreur a probablement à voir avec le typage et le retour implicite dans le rendu. Lorsque vous corrigez cela, vous obtenez finalement quelque chose comme ceci:

const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
    const routeComponent = (props: any) => (
        isAuthenticated
            ? React.createElement(component, props)
            : <Redirect to={{pathname: '/login'}}/>
    );
    return <Route {...rest} render={routeComponent}/>;
};

Ce composant peut être utilisé comme ceci:

<PrivateRoute
    path='/private'
    isAuthenticated={this.props.state.session.isAuthenticated}
    component={PrivateContainer}
/>

Il y a quelques inconvénients avec la solution ci-dessus. L'un d'eux est que vous perdez la sécurité du type.

Étendre probablement le composant Route est la meilleure idée.

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}/>;
        }
    }
}

Vous pouvez donc utiliser le composant comme ceci:

const defaultProtectedRouteProps: ProtectedRouteProps = {
    isAuthenticated: this.props.state.session.isAuthenticated,
    authenticationPath: '/login',
};

<ProtectedRoute
    {...defaultProtectedRouteProps}
    exact={true}
    path='/'
    component={ProtectedContainer}
/>

Mise à jour (novembre 2019)

Si vous préférez écrire des composants fonctionnels, vous pouvez le faire de manière très similaire. Cela fonctionne également avec 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;

Mise à jour (décembre 2019)

Si vous souhaitez rediriger un utilisateur vers le chemin auquel l'utilisateur voulait accéder en premier, vous devez vous souvenir du chemin, afin de pouvoir rediriger après une authentification réussie. La réponse suivante vous guidera à travers cela:

Rediriger un utilisateur vers la page demandée après une authentification réussie avec react-router-dom

51
Robin

Vous pouvez toujours utiliser le formulaire SFC, que je trouve un peu plus propre. Mélangez simplement tous les accessoires dont vous avez besoin avec le RouteProps:

const PrivateRoute: React.SFC<RouteProps> = ({
  component: Component,
  ...rest
}: {
  component: React.ComponentType<RouteProps>;
}) => (
  <Route
    {...rest}
    render={props =>
      fakeAuth.isAuthenticated 
        ? <Component {...props} /> 
        : <Redirect to="/login" />
    }
  />
);
5
Hunter McMillen