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/..."
?
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>
);
}