Search code examples
javascriptreactjsreact-routerreact-router-dom

React Router 6 relative attribute's meaning of Link element


I am using React Router 6.4+ (specifically, 6.22.1) and having the following route config:

const router = createBrowserRouter([
  { path: '/',
    element: <RootLayout />,
    children: [
      { index: true, element: <HomePage /> },
      { path: 'events', element: <EventsPage/> },
      { path: 'events/:eventId', element: <EventDetailPage /> },
      { path: 'events/new', element: <NewEventPage /> },
      { path: 'events/:eventId/edit', element: <EditEventPage /> }
    ]
  }
])

function App() {
  return <RouterProvider router={router}/>;
}

My EventsPage component is:

const DUMMY_EVENTS = [
  {
    id: 'e1',
    title: 'Some events'
  },
  {
    id: 'e2',
    title: 'Another event'
  }
];

function EventsPage() {
  return (
    <>
      <h1>Events Page</h1>
      <ul>
        {DUMMY_EVENTS.map(event => <li key={event.id}>
          <Link to={event.id}>{event.title}</Link>
        </li>)}
      </ul>
    </>
  );
}

export default EventsPage;

Clicking on each event will lead to the EventDetailPage component:

import { Link } from 'react-router-dom';

function EventDetailPage() {
  return (
    <>
      <h1>EventDetailPage</h1>
      <Link
        to='..'
        // relative='path'
      >
        Back
      </Link>
    </>
  );
}

export default EventDetailPage;

Now if we reference to the official documentation here https://reactrouter.com/en/main/components/link#relative the behavior of navigating relative path is inconsistent between going "forward" and "backward":

  • Clicking on each event in the EventsPage (relative='route' by default) means we should go forward relative the route hierarchy. But it's not. It actually navigates to the route of EventDetailPage, which gives me a perception that it is "path relative". And if the behavior is correctly "route hierarchy relative", then where should it navigate to?
  • Clicking on the back link will correctly navigate back up on route level (relative='route') to the root layout, which renders the HomePage as the index route. It will only navigate back up on path segment if I uncomment the relative attribute in EventDetailPage component.

So what I'm looking for is a correct explanation for the relative attribute of Link component here.


Solution

  • Clicking on each event in the EventsPage (relative='route' by default) means we should go forward relative the route hierarchy. But it's not. It actually navigates to the route of EventDetailPage, which gives me a perception that it is "path relative". And if the behavior is correctly "route hierarchy relative", then where should it navigate to?

    When on "/events" and using a relative link target, i.e. "e1" and "e2", this is the same as using "./e1" and "./e2" where the "." represents the current route (if using relative="route") or the current path (if using relative="path") and the result is that you are correctly navigated from "/events" to "/events/:eventId".

    Similarly, if you instead used "../e1" or "../e2" link targets the ".." refers to the parent Route or path, which is RootLayout for relative="route" and "/" for relative="path", and the result would be navigation to "/e1" or "/e2".

    Clicking on the back link will correctly navigate back up on route level (relative='route') to the root layout, which renders the HomePage as the index route. It will only navigate back up on path segment if I uncomment the relative attribute in EventDetailPage component.

    So what I'm looking for is a correct explanation for the relative attribute of Link component here.

    I think the docs explain it correctly enough and also include a good code example with comments.

    Link::relative

    By default, links are relative to the route hierarchy (relative="route"), so .. will go up one Route level from the current contextual route. Occasionally, you may find that you have matching URL patterns that do not make sense to be nested, and you'd prefer to use relative path routing from the current contextual route path. You can opt into this behavior with relative="path":

    // Contact and EditContact do not share additional UI layout
    <Route path="/" element={<Layout />}>
      <Route path="contacts/:id" element={<Contact />} />
      <Route
        path="contacts/:id/edit"
        element={<EditContact />}
      />
    </Route>;
    
    function EditContact() {
      // Since Contact is not a parent of EditContact we need to go up one level
      // in the current contextual route path, instead of one level in the Route
      // hierarchy
      return (
        <Link to=".." relative="path">
          Cancel
        </Link>
      );
    }
    

    The basic gist is that normally link navigation does occur relative to the routing structure, e.g. from one Route to another Route, but that sometimes you don't want this behavior, such as in the above example and in your code where you would prefer to use the path structure, e.g. to navigate from one path to another path.

    relative="route"

    A regular <Link to="..">Back</Link> will navigate up the routing tree to { path: "events", element: <EventsPage /> },'s parent route, e.g. back to "/" and render the RootLayout component.

    const router = createBrowserRouter([
      { // <-- (2) to this route
        path: "/",
        element: <RootLayout />,
        children: [
          { index: true, element: <HomePage /> },
          { path: "events", element: <EventsPage /> },
          { // <-- (1) from this route
            path: "events/:eventId",
            element: <EventDetailPage />
          },
          { path: 'events/new', element: <NewEventPage /> },
          { path: 'events/:eventId/edit', element: <EditEventPage /> }
        ]
      }
    ]);
    

    relative="path"

    A <Link to=".." relative="path">Back</Link> will navigate up a path segment, e.g. from "/events/123" to "/events".

    const router = createBrowserRouter([
      {
        path: "/",
        element: <RootLayout />,
        children: [
          { index: true, element: <HomePage /> },
          {
            path: "events", // <-- (2) to this path
            element: <EventsPage />
          },
          {
            path: "events/:eventId", // <-- (1) from this path
            element: <EventDetailPage />
          },
          { path: 'events/new', element: <NewEventPage /> },
          { path: 'events/:eventId/edit', element: <EditEventPage /> }
        ]
      }
    ]);