You don't build a separate component for every user profile or product page ā you build one template and let dynamic routing feed it the right data based on the URL. This lesson covers defining URL parameters, extracting them with useParams, and reacting correctly when they change.
1What is Dynamic Routing?
When building an app like Twitter, you don't hand-write a separate component for every single user ā a UserAlice.jsx and a UserBob.jsx file would be absurd at scale. Instead, you build one Profile component and use dynamic routing to load the correct user's data based on whatever the URL actually contains.
This single-template approach is what lets an application scale to handle thousands of profiles, products, or articles without adding a single new component for each one.
// Dynamic Routing: Scaling your URL architectureInfinite Pages
One template, many URLs
2Defining the Path
To define a dynamic segment in React Router, you use a colon : followed by a variable name inside your path string, such as <Route path="/user/:username" element={<UserProfile />} />. That colon tells the router this segment of the URL is a placeholder, not a literal string to match exactly.
Any value in that position of the URL ā /user/alice, /user/bob, /user/anything ā matches the route and renders the same UserProfile component.
<Route path='/user/:username' element={<UserProfile />} />The Colon Syntax
Marks a segment as a variable.
3Multiple Dynamic Segments
A single route path can define multiple dynamic segments at once, which is especially useful for hierarchical data like an e-commerce catalog or a deeply nested organizational structure ā for example, <Route path="/catalog/:category/:productId" element={<Product />} />.
A URL like /shop/shoes/nike-air-max matches that pattern with category bound to "shoes" and productId bound to "nike-air-max", letting one component render any category/product combination.
import { useParams } from 'react-router-dom';
const { username } = useParams();
// URL: /user/jdoe -> username = 'jdoe'Multiple Variables
/categories/:catId/items/:itemId
4Extracting with useParams
Once a route defines a dynamic segment, the component it renders needs a way to actually read that value out of the current URL. React Router provides the useParams hook exactly for this purpose.
Calling useParams() returns a plain object containing key/value pairs for every dynamic segment in the matched route ā so for a route defined as /user/:username and a URL of /user/jdoe, useParams() returns { username: 'jdoe' }.
<Route path='/catalog/:category/:productId' element={<Product />} />The Extractor
useParams()
5Extracting Multiple Params
When a route has multiple dynamic segments, useParams returns an object with a property for each one, and the property keys exactly match the names you chose in the path string ā no renaming or guessing required.
For a route defined as /shop/:category/:itemId matched against /shop/shoes/104, destructuring const { category, itemId } = useParams() gives you category === 'shoes' and itemId === '104' directly.
useEffect(() => {
fetchUserData(username);
}, [username]);Object Destructuring
Extract all keys instantly.
6Optional Parameters
Sometimes a parameter should be optional ā /users might show a list of everyone, while /users/bob shows just Bob's profile, both handled by the same route. In React Router v6+, adding a question mark after the parameter name, as in path='/users/:userId?', makes that segment optional.
When the optional segment is missing from the URL, useParams() simply returns undefined for that key, so the component can branch its rendering based on whether the value is present.
/* Dynamic Routing Module Completed */Optional Params
Adding the question mark
7Step-by-Step Breakdown
What is Dynamic Routing?. Welcome to Dynamic Routing. When building an application like Twitter, you don't create a separate React component for every single user (UserAlice.jsx, UserBob.jsx). Instead, you create one Profile component and use 'Dynamic Routing' to load the correct user data based on the URL.
Defining the Path. To define a dynamic segment in React Router, you use a colon : followed by a variable name in your path string. This tells the router that this segment of the URL is a variable placeholder, not a literal string.
Multiple Dynamic Segments. A single route path can have multiple dynamic segments! This is incredibly useful for hierarchical data like e-commerce catalogs or deeply nested organizational structures.
Which character is used in the path prop to indicate a dynamic parameter?
- ā$ (Dollar)
- ā: (Colon)
Extracting with useParams. Inside the component that is rendered by the dynamic route, you need to know what the actual value of the URL parameter is. React Router provides the useParams hook specifically for this. It returns an object containing key/value pairs of the dynamic params from the current URL.
Extracting Multiple Params. If your route has multiple dynamic segments, useParams will return an object with multiple properties. The keys of this object will exactly match the names you gave the parameters in your path string.
If your route is path='/blog/:postId', how do you extract the ID inside your component using object destructuring?
- āid
- āpostId
Optional Parameters. Sometimes a parameter is optional. For example, /users might show a list of users, while /users/bob shows Bob's profile. In React Router v6+, you can make a parameter optional by adding a question mark (?) after the parameter name.
Type Safety with Params. IMPORTANT: The values returned by useParams are ALWAYS strings (or undefined if optional). Even if your URL is /users/42, userId will be the string '42', not the number 42. You must parse it yourself if you need a number.
Catch-All Routes (Splat). If you need to capture a dynamic number of segments (e.g., matching any file path like /files/docs/2023/report.pdf), you use a 'splat' route with an asterisk *. It catches everything after that point in the URL.
Responding to Param Changes. When a user navigates from /users/1 to /users/2, the <UserProfile> component DOES NOT UNMOUNT! It simply stays mounted, and the useParams values update. If you need to fetch new data when the URL changes, you MUST put the parameter in your useEffect dependency array.
If a user navigates from /product/shoes to /product/hats, does the <Product> component fully unmount and remount from scratch?
- āYes, because the URL changed completely
- āNo, React Router just updates the params object
Programmatic Navigation with Params. You can navigate to dynamic routes programmatically using the useNavigate hook. You construct the string dynamically, usually using JavaScript template literals, and pass it to the navigate function.
Real-world pattern: Fetching Data. The most common architecture in React is combining Dynamic Routes, useParams, and useEffect. The router provides the ID from the URL, the component mounts, the effect reads the ID and fetches the data from the API, and the component renders the result.
Mastery Achieved. Dynamic routes mastered! You can now build data-driven interfaces that scale infinitely with your content. You understand how to define variables in the URL and extract them in your components. Next up: Switching between routes efficiently!
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Update the Document Title When a Dynamic Route's Data Loads
Because a component rendered by a dynamic route stays mounted across param changes, remember to update `document.title` (or your framework's head-management API) inside the same effect that fetches new data, so assistive technology and browser tab titles reflect the current record.
2Announce Route Content Changes for Screen Reader Users
Navigating from `/users/1` to `/users/2` doesn't trigger a full page reload or unmount, so a screen reader may not automatically announce the new content; consider moving focus to the page heading or using an `aria-live` region when the fetched data for a new param finishes loading.
SEO Implications
- 1
Each Dynamic Route Needs Genuinely Unique, Server-Rendered Content Per URL
A search engine indexes `/products/42` and `/products/43` as separate pages ā if the data behind a dynamic segment isn't rendered into the actual HTML for that specific URL (e.g., via server-side rendering or static generation), crawlers may see identical or empty content across every instance of the route.
- 2
Optional and Splat Segments Can Create Duplicate-Content Ambiguity
Routes like `/users/:userId?` or `/files/*` can make multiple URL shapes resolve to overlapping content; make sure canonical tags or redirects clarify which specific URL is the authoritative one for a given piece of content.
Best Practices
Always Include the URL Parameter in Your Effect's Dependency Array
Because React Router keeps a component mounted across param changes (e.g., navigating from `/users/1` to `/users/2`), any `useEffect` that fetches data based on that param must list it as a dependency, or the component will keep showing stale data from the previous URL.
Parse useParams Values Before Using Them as Numbers
Every value returned by `useParams` is a string, even if it looks numeric ā `parseInt(id, 10)` or `Number(id)` is required before doing arithmetic or strict equality checks against a numeric ID.
Frequent Bugs
Navigating between two URLs matched by the same dynamic route shows stale data from the previous URL.
The component doesn't unmount when only the URL parameter changes ā it stays mounted and `useParams` just returns new values. Add the parameter to the dependency array of the `useEffect` that fetches data, e.g. `useEffect(() => { fetchUser(id); }, [id])`.
A numeric comparison like `id === 42` never matches even though the URL is `/post/42`.
`useParams` always returns strings, so `id` is `'42'`, not `42`. Convert it explicitly with `parseInt(id, 10)` or `Number(id)` before comparing it to a number.
Real-World Examples
Fetching a Blog Post by Its URL Slug
A `/posts/:slug` route renders a `PostPage` component that reads `slug` via `useParams`, fetches the matching post inside a `useEffect` keyed on `slug`, and re-fetches automatically whenever the user navigates to a different post without the component ever unmounting.
function PostPage() {
const { slug } = useParams();
const [post, setPost] = useState(null);
useEffect(() => {
fetchPost(slug).then(setPost);
}, [slug]);
return post ? <Article post={post} /> : <Spinner />;
}