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>
} />
i have two role "parent" and "manager" and i write this code and work it for me:
function App(props) {
return (
<div className="App ">
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/" element={<ProtectedRoute />}>
<Route exact path="/admin" element={<StudentLists />} />
<Route exact path="/student_pk_api/:id" element={<IdFilter />} />
<Route exact path="/absentes" element={<AbsentStu />} />
<Route exact path="/students_by_field_and_level_:grade_api/:studyfield_code" element={<FieldGradeFilter />} />
<Route exact path="/absent_yesterday_api" element={<YesterdayAbs />} />
<Route exact path="/absent_today_api" element={<TodayAbs />} />
</Route>
<Route exact path="/" element={<ProtectedUserRoute />}>
<Route exact path="/users" element={<SchoolDit />} />
</Route>
</Routes>
</div>
);
}
for cheking roles i write two function:
for manager role:
const ProtectedRoute = () => {
const [manager, setmaneger] = useState(localStorage.getItem("role")==="manager"?true:null);
return manager ? <Outlet /> : <Navigate to="/" />;
}
export default ProtectedRoute
for parent role:
const ProtectedUserRoute = () => {
const [parent, setParent] = useState(localStorage.getItem("role")==="parent"?true:null);
return parent ? <Outlet /> : <Navigate to="/" />;
}
export default ProtectedUserRoute
Of course, there are many ways to implement this. I'll share my working example with React Router DOM v6.
The steps :
- Using a hook to get authentication data (you can use context or Redux).
- create the a mapping objects for the protected paths with the roles data
- Defining routes with unique IDs related to each path's roles.
- Combining everything in the ProtectedRoute and retrieving path data using
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"] },
};
- I like this approach because I can also use this mapping for the navigation bar, but you can decide which properties to include.
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
- Combine all in the ProtectdRoute component and utilze the useOutlet
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.
© 2022 - 2024 — McMap. All rights reserved.
Check
which would check if current user is having enough permissions to see this page or not. Google for keywords "authorization" in general and in react app. Maybe you will find some code example. Before you code, it is important to understand it. So, if you focus on understanding, you can write code in your own way. – Subprincipal