React router v6 and relative links from page within route

Viewed 869

Hi am trying to update project using react router to v6. I get the basics but am struggling with relative links.

We have a page that renders reference documentation for a given item by 'id'. The documentation can have links to other 'sibling' material, using sibling id. In other words the user can navigate around the docs without ever leaving the same basic route (parameterised by 'id').

I have made a little repro on codesandbox, with essential code below.

import React from "react";
import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  useParams
} from "react-router-dom";

import "./styles.css";

function GenericPage() {
  const { id } = useParams();
  return (
    <div className="page">
      <p>Document id: {id}</p>
      <div>
        Links from within page don't work:
        <Link to="./foo" className="link">
          Foo
        </Link>
        <Link to="./bar" className="link">
          Bar
        </Link>
      </div>
    </div>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <div>
        Working top nav:
        <Link to="docs/generic/foo" className="link">
          Foo
        </Link>
        <Link to="docs/generic/bar" className="link">
          Bar
        </Link>
      </div>
      <Routes>
        <Route path="/docs/generic/:id" element={<GenericPage />} />
      </Routes>
    </BrowserRouter>
  );
}

Have tried using ../{id}, etc to no avail.

Maybe this is by design but it seems a bit odd to disallow a link to sibling pages using a simple relative name. All pretty normal in regular web dev.

2 Answers

You can nest your parameterized route under the main one, like this:

<Routes>
  <Route path="/docs/generic" element={<GenericPage />}>
    <Route path=":id" element={<GenericPage />} />
  </Route>
</Routes>

The nesting allows the nested path to get tacked onto the end of its parent, so the final route is /docs/generic/:id.

After that, update your in-page links so you only pass the parameter value to the to prop instead of the whole route:

<div>
  <Link to="foo" className="link">
    Foo
  </Link>
  <Link to="bar" className="link">
    Bar
  </Link>
</div>

This allows you to change the actual route value ("/docs/generic") in your router without having to update the links in your GenericPage component.

See it on Codesandbox.

I'm a bit surprised I hadn't run across this one yet, but it seems to be by design. There's a discussion here that you may find illuminating, or frustrating. There does appear to be some workarounds though.

Apparently the ".." means to "go up one Route". When there's only one Route the code is defaulting to the root "/" "route" that the app is being rendered on.

You can restructure your routes to match the path structure.

Example:

<Routes>
  <Route path="/docs">
    <Route path="generic">
      <Route path=":id" element={<GenericPage />} />
    </Route>
  </Route>
</Routes>

Now the relative links appear to work as you and I, and I suspect many others, expect them to.

function GenericPage() {
  const { id } = useParams();
  return (
    <div className="page">
      <p>Document id: {id}</p>
      <div>
        Links from within page now work:
        <Link to="../foo" className="link">
          Foo
        </Link>
        <Link to="../bar" className="link">
          Bar
        </Link>
      </div>
    </div>
  );
}

Edit react-router-v6-and-relative-links-from-page-within-route

Related