Przekaż właściwości komponentów w Private Route z Typescript i React
Wdrażam uwierzytelnione trasy z Typescript i React przy użyciu właściwości renderowania komponentu Route z React Router v4.
Trasy:
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;
Prywatna trasa:
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;
Problem polega na tym, że chcę wywołać komponent ustawiony w rekwizytach, jednak za każdym razem, gdy próbuję, Typescript generuje następujący błąd.
JSX element type 'Component' does not have any construct or call signatures. TS2604
Obraz błędu
Powodem wydaje się być to, że typ komponentu trasy nie jest tym, którego oczekuje Typescript, jak wyjaśniono tutaj: https://github.com/microsoft/TypeScript/issues/28631dlatego właśnie utworzyłem kopię, która ma nowy typ (ComponentToRender).
Czy jest lepszy sposób na wdrożenie tego? Może nadpisanie elementu składnika RouteProps?
Dzięki!
Odpowiedzi
W końcu zrozumiałem błąd i rozwiązałem go! Kluczem było to, że atrybut komponentu i funkcja renderowania są obsługiwane różnymi typami. Używając RouteProps, kompilator Typescript w rzeczywistości ustawia właściwości komponentu jako ComponentType (typ określony dla 'komponentu' zgodnie z RouterProps), którego nie można użyć do funkcji renderowania, jak wydaje się w pliku 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;
}
To zachowanie staje się widoczne po prostu przenosząc funkcję renderowania do pliku Routes i nie korzystając ze składnika PrivateRoute. Poniższy kod działa zgodnie z oczekiwaniami, ponieważ kompilator poprawnie wywnioskuje typ (React.ReactNode).
<Route
path={ROUTES.POINTS_HISTORY}
render={(props) =>
isSignedIn ? <PointsTransactions /> : <Redirect to={ROUTES.LOGIN} />
}
></Route>
Dlatego, aby rozwiązać problem, po prostu tworzę nowy typ z tylko niezbędnymi parametrami dla mojego przypadku użycia.
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;