rwozniak
rwozniak

Reputation: 43

Layout route can't access segments values when used with nested routes with dynamic segments (React Router v6)

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

Answers (1)

Drew Reese
Drew Reese

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>

Edit layout-route-cant-access-segments-values-when-used-with-nested-routes-with-dyna

Upvotes: 1

Related Questions