Reputation: 43
I have this simplified application, where the page layout and navigation are defined in the Shell component and then the nested application routes are defined in a separate component App:
const Shell = () => (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route element={<PageLayout />}>
<Route path="/app/*" element={<App />} />
</Route>
</BrowserRouter>
)
const App = () => (
<Routes>
<Route index element={<Navigate to="projects/0" />} />
<Route path="projects/:projectId" element={<ProjectDetails />} />
</Routes>
)
The PageLayout
component is defined as a dynamic segment-aware header and the Outlet
with rendered content from the App
:
const PageLayout = () => {
const params = useParams();
return (
<>
<h2>Project: {params.projectId}</h2>
<Outlet />
</>
)
}
The problem is that the dynamic segment projectId
is never available in the params, and instead, params
contains only {"*": "projects/xxx"}
.
Such a problem only occurs if the routes with the dynamic segments are defined under a nested Route
element. Meaning, that if all the routes from App
are defined directly in Shell
, then everything works as expected:
const Shell = () => (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/app" element={<PageLayout />}>
<Route index element={<Navigate to="projects/0" />} />
<Route path="projects/:projectId" element={<ProjectDetails />} />
</Route>
</BrowserRouter>
)
I would prefer to keep the nested routes defined in a separate component because App
is expected to be a remote micro-frontend component loaded via modules federation.
Upvotes: 1
Views: 410
Reputation: 202801
This is how the Routes
components work. They "see" the route path params of Routes
components above them that help form their path, but not what's below them. If you prefer to use descendent routes instead of nested routes, then an alternative could be to use the Outlet
context to expose a callback that descendent route components call to pass up a projectId
.
Example:
const PageLayout = () => {
const [projectId, setProjectId] = useState();
return (
<>
<h2>Project: {projectId}</h2>
<Outlet context={{ setProjectId }} />
</>
);
};
Create a PageLayoutWrapper
to "sniff" the projectId
route path param and pass it back up.
const PageLayoutWrapper = () => {
const { projectId } = useParams();
const { setProjectId } = useOutletContext();
useEffect(() => {
setProjectId(projectId);
}, [projectId, setProjectId]);
return <Outlet />;
};
Usage:
<Routes>
<Route path="/login" element={<Login />} />
<Route element={<PageLayout />}>
<Route path="/app/*" element={<App />} />
</Route>
</Routes>
...
<Routes>
<Route index element={<Navigate to="projects/0" />} />
<Route element={<PageLayoutWrapper />}>
<Route path="projects/:projectId" element={<ProjectDetails />} />
</Route>
</Routes>
Upvotes: 1