Passer les accessoires de composant dans Private Route avec Typescript et React
J'implémente des routes authentifiées avec Typescript et React en utilisant les accessoires de rendu du composant Route de React Router v4.
Itinéraires:
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { ROUTES } from 'utils/constants';
import HomePage from 'components/pages/Home';
import GuestLogin from 'components/pages/GuestLogin';
import ProfilePage from 'components/pages/Profile';
import NotFoundPage from 'components/pages/NotFound';
import ResetPassword from 'components/pages/ResetPassword';
import SetPassword from 'components/pages/SetPassword';
import LoginContainer from 'containers/Login';
import PrivateRoute from './PrivateRoute';
const Routes: React.FunctionComponent = () => (
<Switch>
<Route path={ROUTES.LOGIN} component={LoginContainer} exact></Route>
<PrivateRoute
path={ROUTES.HOME}
component={HomePage}
></PrivateRoute>
<Route path={ROUTES.GUEST_LOGIN} component={GuestLogin}></Route>
<Route path={ROUTES.RESET_PASSWORD} component={ResetPassword}></Route>
<Route path={ROUTES.SET_PASSWORD} component={SetPassword}></Route>
<Route path={ROUTES.PROFILE} component={ProfilePage}></Route>
<Route component={NotFoundPage}></Route>
</Switch>
);
export default Routes;
Route privée:
import React from 'react';
import { useAppContext } from 'containers/App/AppContext';
import { RouteProps, Route, Redirect } from 'react-router-dom';
import { ROUTES } from 'utils/constants';
const PrivateRoute: React.FunctionComponent<RouteProps> = ({
component: Component,
...routeProps
}) => {
const { isSignedIn } = useAppContext();
const ComponentToRender = Component as React.ElementType;
return (
<Route
{...routeProps}
render={(props) =>
isSignedIn ? (
<ComponentToRender {...props} />
) : (
<Redirect to={ROUTES.LOGIN} />
)
}
/>
);
};
export default PrivateRoute;
Le problème est que je veux appeler le jeu de composants sur les accessoires, cependant, chaque fois que j'essaye ceci, Typescript lève l'erreur suivante.
JSX element type 'Component' does not have any construct or call signatures. TS2604
Image de l'erreur
La raison semble être que le type de composant pour la Route n'est pas celui que Typescript attend comme expliqué ici: https://github.com/microsoft/TypeScript/issues/28631, donc je viens de créer une copie qui a un nouveau type (ComponentToRender).
Y a-t-il une meilleure façon de mettre en œuvre cela? Peut-être écraser l'élément de composant RouteProps?
Merci!
Réponses
J'ai finalement compris l'erreur et l'ai résolue! La clé était que l'attribut du composant et la fonction de rendu sont traités différemment selon le type. En utilisant RouteProps
, le compilateur Typescript définit en fait les accessoires de composant comme ComponentType (le type spécifié pour 'component' selon RouterProps), qui ne peut pas être utilisé pour la fonction de rendu, comme cela apparaît dans le fichier index.d.ts.
export interface RouteProps {
location?: H.Location;
component?: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
render?: (props: RouteComponentProps<any>) => React.ReactNode;
children?: ((props: RouteChildrenProps<any>) => React.ReactNode) | React.ReactNode;
path?: string | string[];
exact?: boolean;
sensitive?: boolean;
strict?: boolean;
}
Ce comportement devient apparent simplement en déplaçant la fonction de rendu dans le fichier Routes et en n'utilisant pas le composant PrivateRoute. Le code ci-dessous fonctionne comme prévu, car le compilateur déduit correctement le type (React.ReactNode).
<Route
path={ROUTES.POINTS_HISTORY}
render={(props) =>
isSignedIn ? <PointsTransactions /> : <Redirect to={ROUTES.LOGIN} />
}
></Route>
Par conséquent, pour résoudre le problème, je crée simplement un nouveau type avec uniquement les paramètres nécessaires pour mon cas d'utilisation.
import React from 'react';
import { useAppContext } from 'containers/App/AppContext';
import { RouteProps, Route, Redirect } from 'react-router-dom';
import { ROUTES } from 'utils/constants';
type PrivateRouteProps = {
path: RouteProps['path'];
component: React.ElementType;
};
const PrivateRoute: React.FunctionComponent<PrivateRouteProps> = ({
component: Component,
...routeProps
}) => {
const { isSignedIn } = useAppContext();
return (
<Route
{...routeProps}
render={(props) =>
isSignedIn ? <Component /> : <Redirect to={ROUTES.LOGIN} />
}
/>
);
};
export default PrivateRoute;