I have a page layout with 3 separate sections: The sidebar, the header and the main body. I would like to fill the main body from the child route, while parent routes define the header or sidebar - but after trying to work with Providers and Context approaches, I couldn't quite find the right approach.
Here's a baseline / pseudo-code of what I would like to achieve:
Example Layout:
layout.tsx
export function DefaultLayout() {
return (
<div className="sidebar">
<!-- sidebar content goes here -->
</div>
<div className="page">
<div className="page-header">
<!-- header content goes here -->
</div>
<div className="page-body">
<Outlet />
</div>
</div>
);
}
Example routes:
router
import { Route } from "react-router-dom";
<Route element={<DefaultLayout />}>
<Route
path="/dashboard"
sidebar={<DefaultSidebar />}
header={<DashboardHeader />}
>
<Route path="home" element={<DashboardHomePage />}>
<Route path="stats" element={<DashboardStatsPage />}>
<Route path="help" element={<DashboardHelpPage />}>
</Route>
<Route path="/user" sidebar={<UserSidebar />} header={<UserHeader />}>
<Route path="overview" element={<UserOverviewPage />}>
<Route path="accounts" element={<UserAccountsPage />}>
</Route>
</Route>
As seen in the simplified example above, I would like to define the overall Layout in a parent route, while "filling" the layout sections in nested routes. sidebar={}
and header={}
are just pseudo-code for the example, I realize that it's probably part of another element={}
wrapper.
I've tried solving this by creating a <SidebarProvider>
and <HeaderProvider>
component, working with things like useOutletContext
and passing the state downwards, but that didn't seem to work and was quickly getting hopelessly complicated (infinite loops, etc).
So, starting fresh: what would be a method to achieve the above behavior? If it's via providers, how should I structure them?
Edit: To clarify confusions: I want to decouple the Layout, the sidebar, the header and the routes (which hold the body). I think this should be possible with Providers, as I found examples of using Providers for a similar situation, just to set a page title which don't seem to work when I try to extend them with multiple elements. What would a Provider example, that takes a sidebar and a header, look like?
Essentially: I want to pass 2 elements from a child route, to a parent route. How do I do that?
I would suggest adding the header
and sidebar
as props to the DefaultLayout
route component and render them accordingly. Don't forget that in React we use a className
prop instead of class
.
const DefaultLayout = ({ header, sidebar }) => (
<div className="layout">
<div className="sidebar">{sidebar}</div>
<div className="page">
<div className="page-header">{header}</div>
<div className="page-body">
<Outlet />
</div>
</div>
</div>
);
Note that I added a "layout"
class to the overall surrounding element of the entire element in order to set the overall page layout using a grid layout and assigned areas.
Example CSS:
.layout {
display: grid;
grid-template-areas: "sidebar page";
}
.sidebar {
grid-area: sidebar;
}
.page {
grid-area: page;
}
You will then adjust the routes to pass the sidebar and header components to the layout route each.
<Routes>
<Route
element={
<DefaultLayout
sidebar={<DefaultSidebar />}
header={<DashboardHeader />}
/>
}
>
<Route path="/dashboard">
<Route path="home" element={<DashboardHomePage />} />
<Route path="stats" element={<DashboardStatsPage />} />
<Route path="help" element={<DashboardHelpPage />} />
</Route>
</Route>
<Route
element={
<DefaultLayout sidebar={<UserSidebar />} header={<UserHeader />} />
}
>
<Route path="/user">
<Route path="overview" element={<UserOverviewPage />} />
<Route path="accounts" element={<UserAccountsPage />} />
</Route>
</Route>
</Routes>
You can refactor the DefaultLayout
to split the outer-most div
from the inner sidebar and page div
elements.
const DefaultLayout = () => (
<div className="layout">
<Outlet />
</div>
);
const SidebarHeaderLayout = ({ header, sidebar }) => (
<>
<div className="sidebar">{sidebar}</div>
<div className="page">
<div className="page-header">{header}</div>
<div className="page-body">
<Outlet />
</div>
</div>
</>
);
<Routes>
<Route element={<DefaultLayout />}>
<Route
path="/dashboard"
element={
<SidebarHeaderLayout
sidebar={<DefaultSidebar />}
header={<DashboardHeader />}
/>
}
>
<Route path="home" element={<DashboardHomePage />} />
<Route path="stats" element={<DashboardStatsPage />} />
<Route path="help" element={<DashboardHelpPage />} />
</Route>
<Route
path="/user"
element={
<SidebarHeaderLayout
sidebar={<UserSidebar />}
header={<UserHeader />}
/>
}
>
<Route path="overview" element={<UserOverviewPage />} />
<Route path="accounts" element={<UserAccountsPage />} />
</Route>
</Route>
</Routes>