I can't get the info if it is possible or not to have "multiple nested" Outlet in React with router dom. I have some protected routes created using createBrowserRouter
:
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{
index: true,
element: (
<RequireAuth allowedRoles={[ROLES.User, ROLES.Editor, ROLES.Admin]}>
<Activities />
</RequireAuth>
),
loader: activitiesLoader,
},
{
path: "new-activity",
element: (
<RequireAuth allowedRoles={[ROLES.Admin]}>
<NewActivity />
</RequireAuth>
),
action: newActivityAction
},
{
path: "register",
element: <Register />
},
{
path: "login",
element: <Login />
},
]
},
]);
The Layout component is quite simple with an Outlet
:
export default function Layout() {
return (
<>
<div id="nav">
<Nav />
</div>
<div id="content">
<Outlet />
</div>
</>
);
}
But as the root route is protected, it goes through a RequireAuth
middleware that has an Outlet
as well:
const RequireAuth = ({ allowedRoles }) => {
const { auth } = useAuth();
const location = useLocation();
return (
allowedRoles?.includes(auth?.role)
? <Outlet />
: auth?.email
? <Navigate to="/unauthorized" state={{ from: location }} replace />
: <Navigate to="/login" state={{ from: location }} replace />
);
};
The auth works well, but only the Layout is rendered, the "second" Outlet
doesn't render the child elements.
Is it normal, I am not supposed to use 2 Outlet that way?
You can certainly use multiple nested Outlet
components, one at each level of nesting, providing an outlet for the nested routes under it. You are using RequireAuth
like a wrapper component instead of a layout route component.
Refactor the code to render RequireAuth
as layout routes and move the route content to nested routes under it.
Example:
const router = createBrowserRouter([
{
path: "/",
element: <Layout />,
errorElement: <ErrorPage />,
children: [
// rendered into Layout's Outlet
{
element: (
<RequireAuth
allowedRoles={[ROLES.User, ROLES.Editor, ROLES.Admin]}
/>
),
loader: activitiesLoader,
children: [
// rendered into RequireAuth Outlet
{ index: true, element: <Activities /> },
],
},
{
element: <RequireAuth allowedRoles={[ROLES.Admin]} />,
action: newActivityAction,
children: [
// rendered into RequireAuth Outlet
{ path: "new-activity", element: <NewActivity /> },
],
},
{ path: "register", element: <Register /> },
{ path: "login", element: <Login /> },
]
},
]);
Alternatively if you wanted to keep RequireAuth
as a wrapper component then it should consume and render the children
prop in place of the Outlet
.
Example:
const RequireAuth = ({ allowedRoles, children }) => {
const { auth } = useAuth();
const location = useLocation();
return allowedRoles?.includes(auth?.role)
? children
: (
<Navigate
to={auth?.email ? "/unauthorized" : "/login"}
state={{ from: location }}
replace
/>
);
};