LearnContact
Lesson 4023 min read

Route Parameters

Learn how to define dynamic route segments and read URL parameters and query strings with useParams and useSearchParams.

Introduction

In the last lesson, every route you defined pointed at a fixed path — /about, /contact, and so on. Real applications also need dynamic routes, where part of the URL itself is data: a user's ID in /users/42, a product's slug in /products/wireless-mouse, or a search term in a query string like ?q=react.

React Router supports this with dynamic route segments and two Hooks for reading them: useParams for path segments like :id, and useSearchParams for query string values after a ?. This lesson covers both, and puts them together in a worked user-profile example.

What You Will Learn
  • How to define a dynamic route segment, like path="/users/:id".
  • How to read a route parameter with the useParams() Hook.
  • How to read query string parameters with the useSearchParams() Hook.
  • How to build a user-profile page that looks up data using an ID from the URL.

Defining a Dynamic Route Segment

A dynamic segment is written in a Route's path using a colon prefix, like :id. React Router treats that segment as a variable — it matches any value in that position of the URL and makes it available to the matching component.

App.jsx
import { Routes, Route } from 'react-router-dom';
import UserList from './pages/UserList';
import UserProfile from './pages/UserProfile';

function App() {
  return (
    <Routes>
      <Route path="/users" element={<UserList />} />
      <Route path="/users/:id" element={<UserProfile />} />
    </Routes>
  );
}

export default App;
Behavior
Visiting  /users/1   → renders <UserProfile /> with id = "1"
Visiting  /users/42  → renders <UserProfile /> with id = "42"
Visiting  /users/abc → renders <UserProfile /> with id = "abc"

The same UserProfile component handles every one of these URLs — it just needs a way to find out which id was actually requested. That is exactly what useParams provides.

Reading Parameters with useParams

The useParams Hook returns an object containing every dynamic segment defined in the matching route, keyed by the name used after the colon in the path.

UserProfile.jsx
import { useParams } from 'react-router-dom';

function UserProfile() {
  const { id } = useParams();

  return (
    <div>
      <h2>User Profile</h2>
      <p>Viewing details for user ID: {id}</p>
    </div>
  );
}

export default UserProfile;
Visiting /users/42
User Profile
Viewing details for user ID: 42
Multiple Parameters

A route can define more than one dynamic segment, such as path="/users/:id/posts/:postId". useParams() would then return { id, postId }, with both values pulled straight from the URL.

Reading Query Strings with useSearchParams

Query string parameters — the part of a URL after a ? — are read differently, since they are optional key/value pairs rather than part of the route's path structure. The useSearchParams Hook returns a URLSearchParams-like object and a function to update it, similar in spirit to useState.

SearchResults.jsx
import { useSearchParams } from 'react-router-dom';

// Handles a URL like /search?query=react&sort=recent
function SearchResults() {
  const [searchParams, setSearchParams] = useSearchParams();

  const query = searchParams.get('query') || '';
  const sort = searchParams.get('sort') || 'relevance';

  return (
    <div>
      <p>Results for: {query}</p>
      <p>Sorted by: {sort}</p>
      <button onClick={() => setSearchParams({ query, sort: 'newest' })}>
        Sort by Newest
      </button>
    </div>
  );
}

export default SearchResults;
Visiting /search?query=react&sort=recent
Results for: react
Sorted by: recent

[Sort by Newest]  ← updates the URL to ?query=react&sort=newest

Notice that searchParams.get('query') returns null if the key is missing from the URL entirely, which is why the example falls back to an empty string or a default sort order.

Worked Example: A User Profile Page

Let's combine useParams with a small lookup to build a complete user-profile page that reads an ID from the URL and displays the matching user's data.

data/users.js
export const users = [
  { id: '1', name: 'Amol Chipte', role: 'Instructor' },
  { id: '2', name: 'Priya Sharma', role: 'Student' },
  { id: '3', name: 'Rahul Verma', role: 'Student' },
];
pages/UserProfile.jsx
import { useParams, useSearchParams, Link } from 'react-router-dom';
import { users } from '../data/users';

function UserProfile() {
  const { id } = useParams();
  const [searchParams] = useSearchParams();
  const tab = searchParams.get('tab') || 'overview';

  const user = users.find((u) => u.id === id);

  if (!user) {
    return <p>No user found for ID: {id}</p>;
  }

  return (
    <div>
      <h2>{user.name}</h2>
      <p>Role: {user.role}</p>
      <p>Active tab: {tab}</p>

      <nav>
        <Link to={`/users/${id}?tab=overview`}>Overview</Link>
        <Link to={`/users/${id}?tab=activity`}>Activity</Link>
      </nav>
    </div>
  );
}

export default UserProfile;
Visiting /users/2?tab=activity
Priya Sharma
Role: Student
Active tab: activity

Overview   Activity

The :id segment identifies which user to look up, while the ?tab= query string tracks which section of that profile is active — two different kinds of URL data working together on the same page.

Common Mistakes

Avoid These Mistakes
  • Forgetting the colon in the route path, writing path="/users/id" instead of path="/users/:id", which matches only the literal text "id".
  • Assuming useParams() values are numbers — they are always strings, so convert with Number(id) before doing math with them.
  • Using useParams() to read query strings, or useSearchParams() to read path segments — they are not interchangeable.
  • Forgetting that searchParams.get() returns null (not undefined or an empty string) for a missing key, and not accounting for that.

Best Practices

  • Name dynamic segments clearly, like :id or :postId, since that exact name is the key returned by useParams().
  • Always handle the case where a looked-up item is not found, rather than assuming the ID always matches something.
  • Provide sensible fallback values when reading optional query parameters with searchParams.get().
  • Keep query strings for optional, non-identifying data (filters, sort order, tabs) and path segments for identifying data (which resource is being viewed).
  • Convert route parameters to the correct type (e.g. Number(id)) before using them in comparisons or calculations.

Frequently Asked Questions

Can a route have more than one dynamic segment?

Yes. A path like "/users/:id/posts/:postId" defines two segments, and useParams() returns both as { id, postId }.

What type is the value returned by useParams()?

Always a string, even if the URL segment looks numeric. Convert it explicitly with Number() or parseInt() before using it in numeric operations.

Is useSearchParams() the same as reading window.location.search manually?

It achieves a similar result but stays inside React Router's system — it re-renders your component when the query string changes and provides a clean setSearchParams function to update the URL.

What happens if I visit a dynamic route with an ID that does not exist in my data?

Nothing automatic — your component receives whatever string was in the URL via useParams(), and it is your responsibility to check whether matching data actually exists and show a fallback if not.

Can I update just one query parameter without clearing the others?

Yes, but you need to spread the existing values, e.g. setSearchParams({ ...Object.fromEntries(searchParams), sort: 'newest' }), since setSearchParams replaces the entire query string by default.

Key Takeaways

  • A dynamic route segment is defined with a colon, like path="/users/:id".
  • useParams() reads path segments and returns them as an object of strings, keyed by name.
  • useSearchParams() reads and updates query string parameters, similar in spirit to useState.
  • Route params identify which resource to show; query strings usually carry optional filters or view state.
  • Always handle the case where a looked-up ID does not match any known data.

Summary

Dynamic route segments and query strings let a single page component serve an unlimited number of URLs — one user profile page can display any user simply by reading their ID out of the URL with useParams, while useSearchParams handles optional extras like the active tab or sort order.

In this lesson, you defined a dynamic route with :id, read it with useParams, read query strings with useSearchParams, and built a complete user-profile page that combines both. Next, you will learn about nested routes, which let a page render its own set of sub-routes inside a shared layout.

Lesson 40 Completed
  • You can define a dynamic route segment like path="/users/:id".
  • You can read route parameters with the useParams Hook.
  • You can read and update query strings with useSearchParams.
  • You built a user-profile page driven entirely by the URL.
Next Lesson →

Nested Routes