reactjs - Send komponentprops i Privat Rute med Typescript og React
Jeg implementerer autentificerede ruter med Typescript og React ved at bruge render-props fra Route-komponenten fra React Router v4.
Ruter :
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;
Privat rute:
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;
Problemet er, at jeg vil kalde Component-sættet på props, men hver gang jeg prøver dette, kaster Typescript følgende fejl.
JSX element type 'Component' does not have any construct or call signatures. TS2604
Billede af fejlen
Årsagen ser ud til at være, at komponenttypen for Route ikke er den, Typescript forventer, som forklaret her: https://github.com/microsoft/TypeScript/issues/28631, derfor lavede jeg bare en kopi, som har en ny type (ComponentToRender).
Er der en bedre måde at implementere dette på? Måske ved at overskrive RouteProps-komponentelementet?
Tak!
Svar #1
Jeg forstod endelig fejlen og løste den! Nøglen var, at komponentattributten og render-funktionen håndteres forskelligt type-mæssigt. Ved at bruge RouteProps sætter Typescript-kompileren faktisk komponentens props som ComponentType (den type, der er specificeret for 'component' ifølge RouterProps), hvilket ikke kan bruges til render-funktionen, som set i index.d.ts-filen.
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;
}
Denne adfærd bliver tydelig blot ved at flytte render-funktionen ind i Routes-filen og ikke bruge PrivateRoute-komponenten. Koden nedenfor fungerer som forventet, fordi compileren korrekt udleder typen (React.ReactNode).
<Route
path={ROUTES.POINTS_HISTORY}
render={(props) =>
isSignedIn ? <PointsTransactions /> : <Redirect to={ROUTES.LOGIN} />
}
></Route>
Derfor, for at løse problemet, opretter jeg bare en ny type med kun de nødvendige parametre til mit brugstilfælde.
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;
