React Router, how to get data from a dynamic route
By Flavio Copes
Learn how to get data from a dynamic route in React Router using the useParams hook to read the id parameter. Older apps may still use props.match.params.
A very common need, when you use React Router with a dynamic parameter, is to fetch the data we need to show in the page. You read the parameter with the useParams hook. If you’re still on React Router v5, the same value is on props.match.params, and I cover that further down.
For example we have a list of projects, and clicking one goes to the project detail page with the URL /project/PROJECT_ID.
Using the useParams hook
Declare the route with an element prop (this is the syntax since React Router v6, and the package to install is react-router):
<Route path="/project/:id" element={<SingleProject />} />
Notice the /project/:id path. The :id part is the dynamic segment. This means the component will see the dynamic part in the id parameter.
Now in the SingleProject component, we can use the useParams hook to access the id parameter:
import { useParams } from 'react-router'
// older apps may still import from 'react-router-dom'
//...
const { id } = useParams()
In my case I use this id to filter out the data from an array of items, but you can query a database or do whatever you want with it.
A typical pattern is to fetch the data inside a useEffect that depends on id. This way the data reloads when the user navigates from one project to another:
useEffect(() => {
fetch(`/api/projects/${id}`)
.then(res => res.json())
.then(data => setProject(data))
}, [id])
Older API: props.match.params
React Router v5 apps often use the render prop instead, and read the parameter from match.params:
<Route path="/project/:id" render={(props) => <SingleProject {...props} />} />
In the SingleProject component, the one that is responsible for showing the data (as I listed it in the render prop above) we use the props we pass:
function SingleProject(props) {
...
}
Those props contain the params under the match.params property, so we can use object destructuring to get back our id:
const { id } = props.match.params
match is also available in inline rendered routes, sometimes useful because we can use the id parameter to look up the post data in our data source before rendering the component.
The render prop was removed in React Router v6, together with match on props. On v6 and later, useParams is the way, and you do the lookup inside the component, like in the example below.
Watch out: params are always strings
Whatever way you pick, the parameter you get is always a string, even when it looks like a number. Visiting /post/1 gives you '1', not 1.
This bites you when your data uses numeric ids and you compare with strict equality:
posts.find(p => p.id === id) // undefined if id is still a string!
p.id is the number 1, id from the route is the string '1', so the comparison always fails and find() returns undefined. Convert the param first.
Here’s a full working example with the current Route API:
const posts = [
{ id: 1, title: 'First', content: 'Hello world!' },
{ id: 2, title: 'Second', content: 'Hello again!' }
]
const Post = ({ post }) => (
<div>
<h2>{post.title}</h2>
{post.content}
</div>
)
const PostPage = () => {
const { id } = useParams()
const post = posts.find(p => p.id === Number(id))
return <Post post={post} />
}
//...
<Route path="/post/:id" element={<PostPage />} />
Number(id) turns the string back into a number, and the lookup works.
Want me to talk about your product? You can sponsor this site.