I have a React
app with two layouts: GuestLayout
and AuthLayout
. Each of one renders a Navbar
, and Outlet
and a Footer
depending if the user is logged:
AuthLayout.tsx
interface Props {
isAllowed: boolean
redirectPath: string
children: JSX.Element
}
export const AuthLayout = ({ isAllowed, redirectPath, children }: Props) => {
const location = useLocation()
if (!isAllowed) {
return <Navigate to={redirectPath} replace state={{ from: location }} />
}
return (
<div className='flex flex-col h-screen'>
<Navbar />
<main className='flex-1 min-h-max p-8 bg-slate-100 dark:bg-neutral-800'>
{children || <Outlet />}
</main>
<Footer />
</div>
)
}
GuestLayout.tsx
export const GuestLayout = () => (
<div className='flex flex-col h-screen'>
<GuestNavbar />
<main className='flex-1 min-h-max p-8 bg-slate-100 dark:bg-neutral-800'>
<Outlet />
</main>
<Footer />
</div>
)
The Footer
(used in both layouts), display a series of links that can be visited either the user is logged or not, and I don't know hot to do it properly. Here is my router:
app.tsx
<Route element={<GuestLayout />}>
<Route index element={<Login />} />
<Route path='login' element={<Login />} />
<Route path='recover-password' element={<RecoverPassword />} />
<Route path='legal' element={<Outlet />}>
<Route path='privacy' element={<LegalPageComponent page='privacyPolicy' />} />
<Route path='notice' element={<LegalPageComponent page='legalNotice' />} />
</Route>
</Route>
<Route
element={<AuthLayout isAllowed={!!user} redirectPath='/login' children={<Outlet />} />}
>
<Route index element={<Home />} />
<Route path='home' element={<Home />} />
<Route
path='processes'
element={
<Suspense fallback={<>...</>}>
<ProcessManagement />
</Suspense>
}
/>
<Route path='management' element={<Outlet />}>
<Route index element={<Management />} />
<Route path='clients' element={<ClientManagement />} />
<Route path='projects' element={<ProjectManagement />} />
</Route>
<Route path='legal' element={<Outlet />}>
<Route path='privacy' element={<LegalPageComponent page='privacyPolicy' />} />
<Route path='notice' element={<LegalPageComponent page='legalNotice' />} />
</Route>
</Route>
As you can see, I need the legal pages to be displayed in both layouts, but I don't know how to do it. If I do this, it goes always to GuestLayout
.
Is this approach correct?
You can unnest the "/legal"
layout route and render the nested routes in the a new layout that conditionally renders the guest or auth UI.
It looks like the "/"
index route rendering either Login
or Home
has the same issue. Render Login
on only the "/login"
path, and render a "catch-all" path="*"
route to redirect users to "/home"
. If the user is not authenticated then the AuthLayout
will redirect them to "/login"
.
Example:
interface Props {
isAllowed: boolean;
}
export const CommonLayout = ({ isAllowed }: Props) => {
return (
<div className='flex flex-col h-screen'>
{isAllowed ? <Navbar /> : <GuestNavbar />}
<main className='flex-1 min-h-max p-8 bg-slate-100 dark:bg-neutral-800'>
<Outlet />
</main>
<Footer />
</div>;
);
};
<Routes>
<Route element={<GuestLayout />}>
<Route path='login' element={<Login />} />
<Route path='recover-password' element={<RecoverPassword />} />
</Route>
<Route
element={<AuthLayout isAllowed={!!user} redirectPath='/login' />}
>
<Route path='home' element={<Home />} />
<Route
path='processes'
element={
<Suspense fallback={<>...</>}>
<ProcessManagement />
</Suspense>
}
/>
<Route path='management'>
<Route index element={<Management />} />
<Route path='clients' element={<ClientManagement />} />
<Route path='projects' element={<ProjectManagement />} />
</Route>
</Route>
<Route element={<CommonLayout />}>
<Route path='legal'>
<Route
path='privacy'
element={<LegalPageComponent page='privacyPolicy' />}
/>
<Route
path='notice'
element={<LegalPageComponent page='legalNotice' />}
/>
</Route>
</Route>
<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>
Since the guest and auth layouts really only differ in which header they render, UI-wise, it may be an improvement to instead render all routes into the CommonLayout
to get the conditional rendering of one header or the other, and remove the UI/visual markup from AuthLayout
so it's only handling the auth logic.
Example:
interface Props {
isAllowed: boolean;
}
export const CommonLayout = ({ isAllowed }: Props) => {
return (
<div className='flex flex-col h-screen'>
{isAllowed ? <Navbar /> : <GuestNavbar />}
<main className='flex-1 min-h-max p-8 bg-slate-100 dark:bg-neutral-800'>
<Outlet />
</main>
<Footer />
</div>;
);
};
interface Props {
isAllowed: boolean;
redirectPath: string;
}
export const AuthLayout = ({ isAllowed, redirectPath }: Props) => {
const location = useLocation()
return isAllowed
? <Outlet />
: (
<Navigate
to={redirectPath}
replace
state={{ from: location }}
/>
);
};
<Routes>
<Route element={<CommonLayout />}>
<Route path='login' element={<Login />} />
<Route path='recover-password' element={<RecoverPassword />} />
<Route path='legal'>
<Route
path='privacy'
element={<LegalPageComponent page='privacyPolicy' />}
/>
<Route
path='notice'
element={<LegalPageComponent page='legalNotice' />}
/>
</Route>
<Route
element={<AuthLayout isAllowed={!!user} redirectPath='/login' />}
>
<Route path='home' element={<Home />} />
<Route
path='processes'
element={
<Suspense fallback={<>...</>}>
<ProcessManagement />
</Suspense>
}
/>
<Route path='management'>
<Route index element={<Management />} />
<Route path='clients' element={<ClientManagement />} />
<Route path='projects' element={<ProjectManagement />} />
</Route>
</Route>
</Route>
<Route path="*" element={<Navigate to="/home" replace />} />
</Routes>