Search code examples
reactjstypescriptreact-router-dom

Recursive Friend routes in React-Router V6


This React-Router V5 recursive routes example shows exactly what I am trying to achieve: https://v5.reactrouter.com/web/example/recursive-paths

However I am using react-router-dom@6 and after converting my code to V6 the output is not what I would expect.

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

export default function RecursiveExample() {
  return (
    <>
      <Routes>
        <Route path="/:id" element={<Person />} />
      </Routes>
    </>
  );
}

function Person() {
  let { id } = useParams();
  let person = find(parseInt(id as string, 10));

  return (
    <div>
      <h3>{person!.name}’s Friends</h3>

      <ul>
        {person!.friends.map(id => (
          <li key={id}>
            <Link to={`${id}`}>{find(id)?.name}</Link>
          </li>
        ))}
      </ul>

      {/* Outlet will render any nested routes */}
      <Outlet />
    </div>
  );
}

const PEEPS = [
  { id: 0, name: "Michelle", friends: [1, 2, 3] },
  { id: 1, name: "Sean", friends: [0, 3] },
  { id: 2, name: "Kim", friends: [0, 1, 3] },
  { id: 3, name: "David", friends: [1, 2] }
];

function find(id:any) {
  return PEEPS.find(p => p.id === id);
}

When following the link to the next person id I get the console error "No routes matched location "/0/1" "

How can I render these routes recursively while still maintaining the same URL structure as in the V5 example, e.g. "host.com/1/3/2/..."?


Solution

  • The issue is that you are assuming that the root route is rendering nested routes (i.e. a Route rendering another Route directly) when actually you are rendering descendent routes (a routed component rendering another Routes and Route components). By rendering only one route and an outlet, you can't go more than one level deep, e.g. you can render only the root level route, any paths more deep than this don't have any route that matches, e.g. the invariant error you see.

    The Routes component replaced the RRDv5 Switch component, so with understanding that in the RRDv5 recursive paths demo that each Person component is rendering descendent routes, the RRDv6 analog is for Person to render another Routes and recursive Route component. The other main difference between RRD v5 and v6 is that in order for descendent routes to be matchable and renderable their parent route necessarily needs to append the wildcard "*", or splat, matcher to the end of its path.

    Example:

    export default function RecursiveExample() {
      return (
        <Routes>
          <Route index element={<Navigate to="/0" replace />} />
          <Route path="/:id/*" element={<Person />} />
        </Routes>
      );
    }
    
    function find(id?: string | number) {
      return PEEPS.find(p => String(p.id) === String(id));
    }
    
    function Person() {
      const { id } = useParams();
      const person = find(id);
    
      return (
        <div>
          <h3>{person!.name}’s Friends</h3>
    
          <ul>
            {person!.friends.map(id => (
              <li key={id}>
                <Link to={`${id}`}>{find(id)?.name}</Link>
              </li>
            ))}
          </ul>
    
          <Routes>
            <Route path="/:id/*" element={<Person />} />
          </Routes>
        </div>
      );
    }
    

    Edit recursive-friend-routes-in-react-router-v6