What is the best way to conditionally render routes in react-router based on user role. I have a case where not all roles have permission to view certain routes. And also I need to deal with subroutes. So if one of main routes is something like /posts I want only admin and student to access that route and its subroutes (posts/today for example). I am using react-router version 5.3.2.
I has a similar issue with react-router-dom v6, but you can tweak it to your benefits
First create a source of truth wheither a user can or can't access some route, for roles based auth I'd think of a hook like
export function useUserRoles() {
// some logic or api call to get the roles
// for demonstration purposes it's just hard coded
const userRoles: Array<typeof UserRoles[number]> = ['admin', 'root'];
// return the current user roles
return userRoles;
}
Then we make a component that can decide using that hook weither to render the route or redirect to login(or some page)
export function RolesAuthRoute({ children, roles }: { children: ReactNode, roles: Array<typeof UserRoles[number]> }) {
const userRoles = useUserRoles();
const canAccess = userRoles.some(userRole => roles.includes(userRole));
if (canAccess)
return (
<Fragment>
{children}
</Fragment>
);
return (<Navigate to="/dashboard/login" />);
}
then the route defined wraps that component to act as a guard that decide if you can or can't access that route
<Route
path="users"
element={
<Suspense fallback={<ProjectLayoutLoader />}>
<RolesAuthRoute roles={['admin']}>
<ProjectUsersPage />
</RolesAuthRoute>
</Suspense>
} />
Of course, there are many ways to implement this. I'll share my working example with React Router DOM v6.
The steps :
useOutlet.So lets get started.
1. useAuth hook
const token = await getToken()
const useAuth = () => {
const [user, setUser] = useState<user | null>(null);
useEffect(() => {
if (token) {
const isTokenValid = checkTokenValidity(token);
const userData = isTokenValid ? decodeToken(token) : null;
if (isTokenValid) {
setUser(userData);
} else {
setUser(null);
removeCookie("token");
}
} else {
setUser(null);
}
}, []);
return { user };
};
** I assume the inner methods are stright forawrd and no need to expand
Now let's define the router and create the roles mapping object.
2. Define path role mapping object
export const paths = {
boo: { label: "Boo", path: "boo", roles: null },
foo: { label: "Foo", path: "foo", roles: ["admin"] },
zoo: { label: "API", path: "api", roles: ["admin", "manager"] },
};
3. Define the router and utilze the path maping object
const createRouteId = (key: keyof typeof paths) => {
return JSON.stringify({ [key]: paths[key].roles });
};
export const router = createBrowserRouter([
{
path: "/",
element: <ProtectedRoute />,
children: [
{ path: paths.boo.path, element: <Boo/>, id: createRouteId("boo") },
{ path: paths.foo.path, element: <Foo />, id: createRouteId("foo") },
{ path: paths.zoo.path, element: <Zoo/>, id: createRouteId("zoo") }
],
errorElement: <ErrorPage />,
}]
The id prop should be uniqe and include the roles data per path using the method createRouteId
const isRoleValid = (userRoles: string[] | undefined, outletRole: string[] | null) => {
if (!userRoles) return false;
if (!outletRole) return true;
return userRoles.some((role) => outletRole.includes(role));
};
function ProtectdRoute() {
const { user } = useAuth();
const location = useLocation();
const [locationRoles, setLocationRoles] = useState(null);
const getRouteIdFromOutlet = useOutlet();
useEffect(() => {
const routeLocationData = getRouteIdFromOutlet?.props.children.props.match.route;
const locationKey = routeLocationData?.path;
const locationRolesObj = routeLocationData?.id && JSON.parse(routeLocationData?.id);
if (locationRolesObj && locationKey) {
setLocationRoles(locationRolesObj[locationKey]);
}
}, [user, location.pathname]);
if (!isRoleValid(user?.roles, locationRoles))
return <ErrorPage errorProp={{ status: 403, message: "role unauthorized" }} />;
return user ? <Outlet /> : <Login />;
}
the useOutlt is pretty nested but it does contain the id information which include our roles per spesific path.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With