JavaScript and React course Β· Module 6: React Router

URL Parameters in Cosmic Navigation

12 min read
In this lesson10

In interplanetary travels, coordinates and identifiers of celestial bodies are key to navigation. Similarly in React Router, URL parameters allow for precisely directing the user to specific application resources. In this module, we'll learn how to use, read, and manipulate URL parameters in our space applications.

What are URL Parameters?

URL parameters are variable parts of a URL address that can contain dynamic values. In React Router, they come in two main forms:

  1. Path parameters - embedded in the URL structure (e.g., /planets/:planetId)
  2. Query parameters - added after the question mark (e.g., /planets?type=rocky&inhabited=true)

Path Parameters

Path parameters are part of the URL structure itself and are defined by a colon (:) in the route definition.

Defining Routes with Path Parameters

1import { Routes, Route } from 'react-router-dom';
2
3function AppRoutes() {
4  return (
5    <Routes>
6      <Route path="/" element={<Dashboard />} />
7      <Route path="/planets/:planetId" element={<PlanetDetails />} />
8      <Route path="/missions/:year/:missionId" element={<MissionDetails />} />
9    </Routes>
10  );
11}

In the example above:

  • :planetId is a parameter in the path /planets/:planetId
  • :year and :missionId are parameters in the path /missions/:year/:missionId

These paths will match URLs such as:

  • /planets/mars
  • /planets/jupiter
  • /missions/2150/mars-expedition

Accessing Path Parameters with useParams

To read path parameter values in a component, we use the useParams hook:

1import { useParams } from 'react-router-dom';
2
3function PlanetDetails() {
4  const { planetId } = useParams();
5
6  return (
7    <div className="planet-details">
8      <h1>Planet details: {planetId}</h1>
9      {/* Rest of the component using the planetId value */}
10    </div>
11  );
12}

In a more complex component, we can use the parameter to fetch data:

1import { useParams } from 'react-router-dom';
2import { useState, useEffect } from 'react';
3
4function PlanetDetails() {
5  const { planetId } = useParams();
6  const [planetData, setPlanetData] = useState(null);
7  const [loading, setLoading] = useState(true);
8  const [error, setError] = useState(null);
9
10  useEffect(() => {
11    // Reset state when parameter changes
12    setLoading(true);
13    setError(null);
14
15    // Fetch data for the specific planet
16    fetch(`https://api.space-explorer.com/planets/${planetId}`)
17      .then(response => {
18        if (!response.ok) {
19          throw new Error('Unable to fetch planet data');
20        }
21        return response.json();
22      })
23      .then(data => {
24        setPlanetData(data);
25        setLoading(false);
26      })
27      .catch(err => {
28        setError(err.message);
29        setLoading(false);
30      });
31  }, [planetId]); // Re-run the effect when planetId changes
32
33  if (loading) return <div>Loading planet data...</div>;
34  if (error) return <div>Error: {error}</div>;
35  if (!planetData) return <div>Planet not found</div>;
36
37  return (
38    <div className="planet-details">
39      <h1>{planetData.name}</h1>
40      <div className="planet-info">
41        <p>Diameter: {planetData.diameter} km</p>
42        <p>Distance from the Sun: {planetData.distanceFromSun} million km</p>
43        <p>Surface temperature: {planetData.surfaceTemperature}Β°C</p>
44        <p>Type: {planetData.type}</p>
45      </div>
46      {/* More information about the planet */}
47    </div>
48  );
49}

Optional Path Parameters

The mission log has two views: a list of all missions at /missions and the missions from a single year, e.g. /missions/2150. So the year is sometimes in the address and sometimes not. Since version 6.5 (December 2022, and the same in v7 and v8), React Router lets you mark such a segment as optional: just add a question mark at its end.

1<Routes>
2  <Route path="/missions/:year?" element={<MissionsList />} />
3</Routes>

One route now handles both addresses: /missions and /missions/2150. The question mark applies only to the segment it is attached to, which is why /missions/2150/06 does not match this route. The /missions/:year?/:month? pattern from the routing configuration lesson works the same way: each of the two segments is optional on its own.

You read the parameter as before, with the useParams hook. The only new thing is that for an address without a year, year is undefined, so the component has to handle both cases:

1import { useParams } from 'react-router-dom';
2
3function MissionsList() {
4  const { year } = useParams();
5
6  return <h2>{year ? `Missions from ${year}` : 'All missions'}</h2>;
7}

The component does not know which address it was rendered for, and it does not need to. It receives year or undefined and picks the heading based on that. A static segment, meaning a plain word in the path rather than a parameter, can be optional too:

1<Route path="/crew/:memberId/edit?" element={<CrewMember />} />

This route matches both /crew/ra and /crew/ra/edit, and renders the same CrewMember component in both cases.

In versions 6.0 to 6.4 the question mark did not work, so you had to define two separate routes with the same element. This form is still valid and you will come across it in older projects:

1<Routes>
2  <Route path="/missions" element={<MissionsList />} />
3  <Route path="/missions/:year" element={<MissionsList />} />
4</Routes>

The third option is query parameters, which are optional by nature. I recommend a simple rule: choose an optional segment when the value builds the address hierarchy, like the year in the mission log, and query parameters when the values are filters that can be combined freely. You will meet query parameters in the next section.

Query Parameters

Query parameters are key-value pairs added after the question mark (?) in the URL. Example: /search?query=mars&category=planets

Accessing Query Parameters with useSearchParams

The useSearchParams hook allows reading and manipulating query parameters:

1import { useSearchParams } from 'react-router-dom';
2
3function PlanetSearch() {
4  // searchParams is a URLSearchParams object
5  // setSearchParams is a function to update the parameters
6  const [searchParams, setSearchParams] = useSearchParams();
7
8  // Reading parameter values
9  const query = searchParams.get('query') || '';
10  const type = searchParams.get('type') || 'all';
11
12  // Function handling the search
13  const handleSearch = (e) => {
14    e.preventDefault();
15    const formData = new FormData(e.target);
16    const newQuery = formData.get('searchQuery');
17    const newType = formData.get('planetType');
18
19    // Update query parameters
20    setSearchParams({ query: newQuery, type: newType });
21  };
22
23  return (
24    <div className="planet-search">
25      <h1>Planet Search Engine</h1>
26
27      <form onSubmit={handleSearch}>
28        <input
29          name="searchQuery"
30          defaultValue={query}
31          placeholder="Enter planet name..."
32        />
33
34        <select name="planetType" defaultValue={type}>
35          <option value="all">All types</option>
36          <option value="rocky">Rocky</option>
37          <option value="gas-giant">Gas giants</option>
38          <option value="ice-giant">Ice giants</option>
39        </select>
40
41        <button type="submit">Search</button>
42      </form>
43
44      <div className="search-results">
45        {/* Search results depending on query and type */}
46        <p>Search phrase: {query || '(none)'}</p>
47        <p>Planet type: {type}</p>
48      </div>
49    </div>
50  );
51}

Manipulating Query Parameters

We can manipulate query parameters in different ways:

1// Set new parameters (replaces all existing ones)
2setSearchParams({ query: 'mars', type: 'rocky' });
3
4// Add or update a parameter without removing existing ones
5setSearchParams(params => {
6  params.set('query', 'mars');
7  return params;
8});
9
10// Remove a parameter
11setSearchParams(params => {
12  params.delete('type');
13  return params;
14});
15
16// Check if a parameter exists
17const hasType = searchParams.has('type');
18
19// Get all values for a parameter (if it occurs multiple times)
20const allCategories = searchParams.getAll('category');

Query Parameters vs. Path Parameters - When to Use Which?

Path Parameters:

  • For identifying the main resource (e.g., /planets/mars)
  • When the parameter is required
  • When the URL structure should reflect the resource hierarchy
  • Examples: planet ID, mission year, constellation name

Query Parameters:

  • For filtering, sorting, pagination
  • For optional parameters
  • When there are many parameter combinations
  • Examples: search criteria, results page, limits, filters

Combining Path and Query Parameters

We can combine both types of parameters for greater flexibility:

1// URL: /planets/mars?tab=geology&unit=metric
2
3import { useParams, useSearchParams } from 'react-router-dom';
4
5function PlanetDetails() {
6  const { planetId } = useParams();
7  const [searchParams] = useSearchParams();
8
9  const activeTab = searchParams.get('tab') || 'overview';
10  const unit = searchParams.get('unit') || 'metric';
11
12  return (
13    <div>
14      <h1>Planet: {planetId}</h1>
15
16      <div className="tabs">
17        <TabLink to={`/planets/${planetId}?tab=overview&unit=${unit}`}
18          active={activeTab === 'overview'}>
19          Overview
20        </TabLink>
21        <TabLink to={`/planets/${planetId}?tab=geology&unit=${unit}`}
22          active={activeTab === 'geology'}>
23          Geology
24        </TabLink>
25        <TabLink to={`/planets/${planetId}?tab=atmosphere&unit=${unit}`}
26          active={activeTab === 'atmosphere'}>
27          Atmosphere
28        </TabLink>
29      </div>
30
31      <div className="unit-switcher">
32        <UnitToggle
33          metric={unit === 'metric'}
34          onChange={(isMetric) => {
35            const newUnit = isMetric ? 'metric' : 'imperial';
36            // Update only the unit parameter, preserve tab
37            searchParams.set('unit', newUnit);
38            setSearchParams(searchParams);
39          }}
40        />
41      </div>
42
43      {/* Content depending on activeTab and unit */}
44    </div>
45  );
46}

createSearchParams - Simplifying Query Parameter Creation

The createSearchParams function helps create query parameters from JavaScript objects:

1import { createSearchParams, useNavigate } from 'react-router-dom';
2
3function PlanetFilterPanel() {
4  const navigate = useNavigate();
5
6  const applyFilters = (filters) => {
7    // Convert filter object to URL parameters
8    const params = createSearchParams({
9      type: filters.type,
10      minSize: filters.minSize,
11      hasWater: filters.hasWater ? 'true' : undefined,
12      sortBy: filters.sortBy
13    });
14
15    // Navigate with parameters
16    navigate({
17      pathname: '/planets',
18      search: `?${params}`
19    });
20  };
21
22  return (
23    <form onSubmit={(e) => {
24      e.preventDefault();
25      const form = e.target;
26      applyFilters({
27        type: form.type.value,
28        minSize: form.minSize.value,
29        hasWater: form.hasWater.checked,
30        sortBy: form.sortBy.value
31      });
32    }}>
33      {/* Filter form */}
34      <button type="submit">Apply filters</button>
35    </form>
36  );
37}

When creating links with parameters, we can use:

For path parameters:

1// Simple string concatenation
2<Link to={`/planets/${planetId}`}>
3  Planet details {planetName}
4</Link>
5
6// Using an object (more flexible)
7<Link to={{
8  pathname: `/planets/${planetId}`,
9}}>
10  Planet details {planetName}
11</Link>

For query parameters:

1import { Link, createSearchParams } from 'react-router-dom';
2
3// Using createSearchParams
4<Link to={{
5  pathname: '/planets',
6  search: `?${createSearchParams({
7    type: 'rocky',
8    inhabited: 'true'
9  })}`
10}}>
11  Inhabited rocky planets
12</Link>
13
14// Or manually creating parameters
15<Link to={`/planets?type=rocky&inhabited=true`}>
16  Inhabited rocky planets
17</Link>

Full Example Application with URL Parameters

Let's now see a complete space application example using both path parameters and query parameters:

1import React, { useState, useEffect } from 'react';
2import {
3  BrowserRouter,
4  Routes,
5  Route,
6  Link,
7  useParams,
8  useSearchParams,
9  createSearchParams,
10  useNavigate
11} from 'react-router-dom';
12
13// API simulation
14const planetsData = [
15  { id: 'mercury', name: 'Mercury', type: 'rocky', diameter: 4879, hasWater: false },
16  { id: 'venus', name: 'Venus', type: 'rocky', diameter: 12104, hasWater: false },
17  { id: 'earth', name: 'Earth', type: 'rocky', diameter: 12742, hasWater: true },
18  { id: 'mars', name: 'Mars', type: 'rocky', diameter: 6779, hasWater: true },
19  { id: 'jupiter', name: 'Jupiter', type: 'gas-giant', diameter: 139820, hasWater: true },
20  { id: 'saturn', name: 'Saturn', type: 'gas-giant', diameter: 116460, hasWater: true },
21  { id: 'uranus', name: 'Uranus', type: 'ice-giant', diameter: 50724, hasWater: true },
22  { id: 'neptune', name: 'Neptune', type: 'ice-giant', diameter: 49244, hasWater: true },
23];
24
25// Main components
26function PlanetsList() {
27  const [searchParams, setSearchParams] = useSearchParams();
28  const navigate = useNavigate();
29
30  // Read query parameters
31  const type = searchParams.get('type') || 'all';
32  const hasWater = searchParams.get('hasWater');
33  const sortBy = searchParams.get('sortBy') || 'name';
34
35  // Filter planets
36  const filteredPlanets = planetsData
37    .filter(planet => type === 'all' || planet.type === type)
38    .filter(planet => hasWater === null || (hasWater === 'true' && planet.hasWater))
39    .sort((a, b) => {
40      if (sortBy === 'name') return a.name.localeCompare(b.name);
41      if (sortBy === 'diameter') return b.diameter - a.diameter;
42      return 0;
43    });
44
45  // Function to update filters
46  const updateFilters = (newFilters) => {
47    const params = createSearchParams({
48      ...Object.fromEntries(searchParams),
49      ...newFilters
50    });
51    setSearchParams(params);
52  };
53
54  return (
55    <div className="planets-list">
56      <h1>Planet Catalog</h1>
57
58      <div className="filters">
59        <label>
60          Planet type:
61          <select
62            value={type}
63            onChange={(e) => updateFilters({ type: e.target.value })}
64          >
65            <option value="all">All</option>
66            <option value="rocky">Rocky</option>
67            <option value="gas-giant">Gas giants</option>
68            <option value="ice-giant">Ice giants</option>
69          </select>
70        </label>
71
72        <label>
73          <input
74            type="checkbox"
75            checked={hasWater === 'true'}
76            onChange={(e) => updateFilters({ hasWater: e.target.checked ? 'true' : null })}
77          />
78          With water only
79        </label>
80
81        <label>
82          Sort by:
83          <select
84            value={sortBy}
85            onChange={(e) => updateFilters({ sortBy: e.target.value })}
86          >
87            <option value="name">Name</option>
88            <option value="diameter">Size</option>
89          </select>
90        </label>
91      </div>
92
93      <ul className="planets-grid">
94        {filteredPlanets.map(planet => (
95          <li key={planet.id} className={`planet ${planet.type}`}>
96            <Link to={`/planets/${planet.id}`}>
97              <h2>{planet.name}</h2>
98              <p>Type: {planet.type}</p>
99              <p>Diameter: {planet.diameter} km</p>
100              <p>Water: {planet.hasWater ? 'Present' : 'Absent'}</p>
101            </Link>
102          </li>
103        ))}
104      </ul>
105
106      {filteredPlanets.length === 0 && (
107        <p>No planets found matching the criteria.</p>
108      )}
109
110      <button onClick={() => navigate('/')}>
111        Return to control center
112      </button>
113    </div>
114  );
115}
116
117function PlanetDetails() {
118  const { planetId } = useParams();
119  const [searchParams, setSearchParams] = useSearchParams();
120  const navigate = useNavigate();
121
122  // Read query parameters
123  const activeTab = searchParams.get('tab') || 'overview';
124
125  // Find planet with given ID
126  const planet = planetsData.find(p => p.id === planetId);
127
128  // If planet not found
129  if (!planet) {
130    return (
131      <div className="planet-not-found">
132        <h1>Planet does not exist!</h1>
133        <p>No planet found with identifier: {planetId}</p>
134        <button onClick={() => navigate('/planets')}>
135          Return to planet catalog
136        </button>
137      </div>
138    );
139  }
140
141  // Change active tab
142  const changeTab = (tab) => {
143    setSearchParams({ tab });
144  };
145
146  return (
147    <div className="planet-details">
148      <h1>{planet.name}</h1>
149
150      <div className="tabs">
151        <button
152          className={activeTab === 'overview' ? 'active' : ''}
153          onClick={() => changeTab('overview')}
154        >
155          Overview
156        </button>
157        <button
158          className={activeTab === 'composition' ? 'active' : ''}
159          onClick={() => changeTab('composition')}
160        >
161          Composition
162        </button>
163        <button
164          className={activeTab === 'exploration' ? 'active' : ''}
165          onClick={() => changeTab('exploration')}
166        >
167          Exploration
168        </button>
169      </div>
170
171      <div className="tab-content">
172        {activeTab === 'overview' && (
173          <div>
174            <h2>Overview of planet {planet.name}</h2>
175            <p>Type: {planet.type}</p>
176            <p>Diameter: {planet.diameter} km</p>
177            <p>Water: {planet.hasWater ? 'Present' : 'Absent'}</p>
178          </div>
179        )}
180
181        {activeTab === 'composition' && (
182          <div>
183            <h2>Composition of planet {planet.name}</h2>
184            <p>Information about planet composition will appear here...</p>
185          </div>
186        )}
187
188        {activeTab === 'exploration' && (
189          <div>
190            <h2>Exploration of planet {planet.name}</h2>
191            <p>Information about exploration missions will appear here...</p>
192          </div>
193        )}
194      </div>
195
196      <Link to="/planets">Return to planet catalog</Link>
197    </div>
198  );
199}
200
201function App() {
202  return (
203    <BrowserRouter>
204      <div className="space-app">
205        <header>
206          <h1>Cosmic Navigation System</h1>
207        </header>
208
209        <main>
210          <Routes>
211            <Route path="/" element={<Dashboard />} />
212            <Route path="/planets" element={<PlanetsList />} />
213            <Route path="/planets/:planetId" element={<PlanetDetails />} />
214            <Route path="*" element={<NotFound />} />
215          </Routes>
216        </main>
217
218        <footer>
219          <p>&copy; 2150 Interplanetary Space Agency</p>
220        </footer>
221      </div>
222    </BrowserRouter>
223  );
224}
225
226// Other components
227function Dashboard() {
228  return (
229    <div className="dashboard">
230      <h1>Mission Control Center</h1>
231      <p>Welcome to the cosmic navigation system!</p>
232      <Link to="/planets">Browse planet catalog</Link>
233    </div>
234  );
235}
236
237function NotFound() {
238  return (
239    <div className="not-found">
240      <h1>Navigation Error: 404</h1>
241      <p>Lost in space! This page does not exist.</p>
242      <Link to="/">Return to Control Center</Link>
243    </div>
244  );
245}
246
247export default App;

Best Practices for URL Parameters

  1. Use readable parameter names - they should describe what they represent
  2. Choose the appropriate type of parameters - path parameters for main resources, query parameters for filters
  3. Handle missing parameters - always provide default values
  4. Validate parameters - check if values are correct before using them
  5. Preserve query parameter state - update only the parameters that change
  6. Use parameters to store UI state - this makes it easier to share and save state

Summary

URL parameters are a powerful tool in cosmic navigation (and React Router). They allow for:

  • Identifying specific resources (path parameters)
  • Filtering and sorting data (query parameters)
  • Remembering the user interface state
  • Creating shareable and bookmarkable links
  • Handling browser history

Thanks to them, users can precisely navigate to specific locations in the space application, and you as a programmer can create interactive and user-friendly interfaces.

Code for this lesson: App.jsx
1import React from 'react';
2import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
3
4// Planet list
5const planets = [
6  { id: 'mars', name: 'Mars', emoji: '', distance: '225M km' },
7  { id: 'jupiter', name: 'Jupiter', emoji: '', distance: '628M km' },
8  { id: 'saturn', name: 'Saturn', emoji: '', distance: '1.2B km' },
9  { id: 'neptune', name: 'Neptune', emoji: '', distance: '4.3B km' },
10];
11
12// Home page with planet list
13function PlanetList() {
14  return (
15    <div className="planet-list">
16      <h1>Planet Catalog</h1>
17      <div className="planets-grid">
18        {planets.map(planet => (
19          <Link
20            key={planet.id}
21            to={`/planet/${planet.id}`}
22            className="planet-card"
23          >
24            <div className="planet-emoji">{planet.emoji}</div>
25            <h3>{planet.name}</h3>
26            <p>{planet.distance}</p>
27          </Link>
28        ))}
29      </div>
30    </div>
31  );
32}
33
34// Planet details - using useParams
35function PlanetDetails() {
36  const { planetId } = useParams();
37  const planet = planets.find(p => p.id === planetId);
38
39  if (!planet) {
40    return (
41      <div className="not-found">
42        <h1>Planet not found</h1>
43        <p>No planet found with ID: {planetId}</p>
44        <Link to="/" className="back-link">← Back to catalog</Link>
45      </div>
46    );
47  }
48
49  return (
50    <div className="planet-details">
51      <Link to="/" className="back-link">← Back to catalog</Link>
52      <div className="planet-hero">
53        <div className="planet-emoji-large">{planet.emoji}</div>
54        <h1>{planet.name}</h1>
55      </div>
56      <div className="planet-info">
57        <div className="info-item">
58          <span className="label">ID:</span>
59          <span className="value">{planet.id}</span>
60        </div>
61        <div className="info-item">
62          <span className="label">Distance from Earth:</span>
63          <span className="value">{planet.distance}</span>
64        </div>
65        <div className="info-item">
66          <span className="label">URL parameter:</span>
67          <span className="value code">:planetId = "{planetId}"</span>
68        </div>
69      </div>
70    </div>
71  );
72}
73
74function App() {
75  return (
76    <BrowserRouter>
77      <div className="app">
78        <Routes>
79          <Route path="/" element={<PlanetList />} />
80          <Route path="/planet/:planetId" element={<PlanetDetails />} />
81        </Routes>
82      </div>
83    </BrowserRouter>
84  );
85}
86
87export default App;

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What is the key difference between the Link and NavLink components?

Hands-on tasks in the game

  • Horizontal ordering

    Arrange the Route syntax elements in React Router v6:

  • Code editor

    Dynamic URL parameters

  • Vertical ordering

    Arrange the routing hierarchy in a space application:

Useful articles