For learning purposes I am trying to create a custom Route, which is protected, using React-Router v6. As many people have tried before me in Forums or here, I get this error: Error: [ProtectedRoute] is not a component.
Also if someone is struggling with a non-reusable implementation can see my working code below: My problem is that I would really like it to be reusable so that I can avoid having this messy kind of code. Let me know if you have any ideas.
I personally think that it's kinda impossible because of the current V6 implementation. Maybe they will let us do it in the future. But I hope that this post will help anyway people who would like to have protected routes especially while using the Parse Platform.
Custom Route:
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
<AuthWrapper>
<Component {...rest} {...props} />
</AuthWrapper>;
}}
/>
);
};
Wrapper using Parse Server to protect the routes if the user isn't currently logged in.
const AuthWrapper = ({ children }) => {
if (Parse.User.current() !== null) {
let isAuthenticated = Parse.User.current().getSessionToken();
let authCondition = isAuthenticated !== undefined;
if (authCondition) {
return children;
}
}
return <Navigate to={"/login"} />;
};
export default AuthWrapper;
Working solution without custom Route: (ofc without <ProtectedRoute />
)
<Route
path="/book"
element={
<AuthWrapper>
<BookPage />
</AuthWrapper>
}
/>
What I would like to do instead:
<ProtectedRoute
path={'/path'}
element={<Anything/>}
/>
For learning purposes I am trying to create a custom Route, which is protected, using React-Router v6. As many people have tried before me in Forums or here, I get this error: Error: [ProtectedRoute] is not a component.
Also if someone is struggling with a non-reusable implementation can see my working code below: My problem is that I would really like it to be reusable so that I can avoid having this messy kind of code. Let me know if you have any ideas.
I personally think that it's kinda impossible because of the current V6 implementation. Maybe they will let us do it in the future. But I hope that this post will help anyway people who would like to have protected routes especially while using the Parse Platform.
Custom Route:
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
<AuthWrapper>
<Component {...rest} {...props} />
</AuthWrapper>;
}}
/>
);
};
Wrapper using Parse Server to protect the routes if the user isn't currently logged in.
const AuthWrapper = ({ children }) => {
if (Parse.User.current() !== null) {
let isAuthenticated = Parse.User.current().getSessionToken();
let authCondition = isAuthenticated !== undefined;
if (authCondition) {
return children;
}
}
return <Navigate to={"/login"} />;
};
export default AuthWrapper;
Working solution without custom Route: (ofc without <ProtectedRoute />
)
<Route
path="/book"
element={
<AuthWrapper>
<BookPage />
</AuthWrapper>
}
/>
What I would like to do instead:
<ProtectedRoute
path={'/path'}
element={<Anything/>}
/>
Share
Improve this question
edited Apr 22, 2023 at 18:40
Kas Elvirov
7,9804 gold badges46 silver badges65 bronze badges
asked Jan 15, 2022 at 18:32
botana_devbotana_dev
5281 gold badge6 silver badges17 bronze badges
3 Answers
Reset to default 14react-router-dom
v6 doesn't, and I don't suspect it ever will, support custom route components like was used in previous versions, preferring now composition. You've correctly created an AuthWrapper
component that wraps some content, and this is the v6 authentication example from the docs.
But it can be improved upon. Instead of returning a single children
node you can instead return an Outlet
for nested Route
components to be rendered into. This converts the AuthWrapper
component from a wrapper component to a layout component.
import { Navigate, Outlet } from 'react-router-dom';
const AuthLayout = () => {
if (Parse.User.current() !== null) {
const isAuthenticated = Parse.User.current().getSessionToken();
return isAuthenticated ? <Outlet /> : null; // or loading indicator, etc...
}
return <Navigate to={"/login"} replace />;
};
This allows you to render a single AuthLayout
component into a Route
, and nest any number of protected routes into it.
<Route element={<AuthLayout />}>
<Route path="/book" element={<BookPage />} />
... other protected routes ...
</Route>
I think this will fix it. You missed the return in the render prop.
const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
return (<AuthWrapper>
<Component {...rest} {...props} />
</AuthWrapper>);
}}
/>
);
};
I did it by doing two things
- creating extended route object (RouteObject with my own props)
- mapping them
- by adding wrapper with protection and custom logic
- filtering back from ExtendedRouteObject to RouteObject
So, there is my code
// my service types
type Diff<T extends keyof any, U extends keyof any> = ({ [P in T]: P } & {
[P in U]: never;
} & {
[x: string]: never;
})[T];
export type Overwrite<T, U> = Pick<T, Diff<keyof T, keyof U>> & U;
// Overwrite is my custom type in order to override any type i need
type TExtendedRouteObject = Overwrite<
RouteObject,
{
children?: TExtendedRouteObject[];
}
> & {
/**
* In order to set to document.title
*/
title: string;
};
const mapExtendedRoutes = (routeObjects: TExtendedRouteObject[]): RouteObject[] => {
if (routeObjects.length) {
return routeObjects.map(route => {
if (route.children?.length) {
return omit({
...route,
element: <RouteWrapper {...route} />,
children: mapExtendedRoutes(route.children),
},
['title'],
) as RouteObject;
}
return omit({ // omit from lodash.omit
...route,
element: <RouteWrapper {...route} />,
}) as RouteObject;
});
}
return [];
};
const RouteWrapper: FC<TExtendedRouteObject> = ({ element, title }) => {
useEffect(() => {
document.title = title;
}, [title]);
// your auth logic goes here
return <>{element}</>;
};
const extendedRoutes: TExtendedRouteObject[] = [
{
path: '/',
title: 'Homepage',
element: <Dashboard />,
children: [...],
},
...
];
const routes: RouteObject[] = mapExtendedRoutes(extendedRoutes);
const router = createBrowserRouter(routes);
// then pass it to <RouterProvider router={router} />