I am trying to set up some react router routes, using the newest v6 specifically. What I'd like to do is have a website with a rest-like hierarchy of pages, and be able to parse out the IDs easily. Specifically with two level of IDs on the path.
I'm having trouble with how I'm supposed to properly parse out the IDs from the url using react router paradigms. I could just use window.location.pathname
and parse it myself or with a lib like it was 2003, but theres got to be a more react way to do it.
Here is the basic desired structure of urls:
/collection/ (all collections)
/collection/123 (collection 123)
/collection/123/abc (collection 123, subgroup abc)
/collection/123/all (special handler, collection 123, all subgroups)
It seems I can use useParams()
, however when I console.log
that within my Screen it does not seem very helpful or structured at all. See below.
Should there be a better way to pull out the :id
or :groupId
specified in the routes? Instead of just one property for "*" that I still end up needing to parse myself anyway. I was hoping for a "id" and "groupId" property, but maybe I'm approaching it wrong.
// App.tsx
<BrowserRouter>
<Routes>
<Route path="/collection/*" element={<CollectionScreen />} />
</Routes>
</BrowserRouter>
// CollectionScreen.tsx
const params = useParams()
console.log(params);
// {*: ''} // path: /collection
// {*: '123'} // path: /collection/123
// {*: '123/abc'} // path: /collection/123/abc
...
<Routes>
<Route path=":id/*" element={<Collection />} />
<Route path=":id/all" element={<CollectionAll />} />
<Route path=":id/:groupId" element={<CollectionGroup />} />
</Routes>
EDIT:
Based on solution below, this route structure does what I need. I didn't want to use the element
in the subroutes in my App.tsx as things are a little more complicated and I want to keep App.tsx light. Removing the element
property however works fine. I then just use useParams()
in the Screen to access all the params as I originally needed.
// App.tsx
<Route path="/collection/*" element={<CollectionsScreen />}>
<Route path=":id/*" />
<Route path=":id/all" />
<Route path=":id/:groupId" />
</Route>
// CollectionScreen.tsx
const params = useParams<{ id?: string; groupId?: string }>();
const { id, groupId } = params;
...
// simplified here, but easy access to id/groupId here is helpful
{!id && <Collection />}
{id && groupId && groupId === "all" && <CollectionAll />}
{id && groupId && groupId !== "all" && <CollectionGroup />}
CodePudding user response:
The useParams
hook only returns the route params within the current Routes
component scope.
For example, if the path is "/collection/123/456"
then:
CollectionScreen
sees{*: "123/456"}
Where the
*
is from the parentCollectionScreen
route.CollectionGroup
sees{*: "123/456", id: "123", groupId: "456"}
Where the
*
is from the parentCollectionScreen
route andid
andgroupId
from the childCollectionGroup
component.
The CollectionScreen
can't see route params further down because it is rendering a Routes
component which effectively "gates" the params below it. A small refactor of the routes can allow CollectionScreen
to see the route params routes below it.
App
<BrowserRouter>
<Routes>
<Route path="/collection/*">
<Route path=":id" element={<CollectionScreen />}>
<Route index element={<Collection />} />
<Route path="all" element={<CollectionAll />} />
<Route path=":groupId" element={<CollectionGroup />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
CollectionScreen
const params = useParams();
console.log("CollectionScreen", params);
return (
<>
...
<Outlet />
</>
);
Now all routed components see the same params
object.
CollectionScreen {*: "123/456", id: "123", groupId: "456"} CollectionGroup {*: "123/456", id: "123", groupId: "456"}