JavaScript and React course Β· Module 6: React Router
URL Parameters in Cosmic Navigation
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:
- Path parameters - embedded in the URL structure (e.g.,
/planets/:planetId) - 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:
:planetIdis a parameter in the path/planets/:planetId:yearand:missionIdare 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}Generating Links with Parameters
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>© 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
- Use readable parameter names - they should describe what they represent
- Choose the appropriate type of parameters - path parameters for main resources, query parameters for filters
- Handle missing parameters - always provide default values
- Validate parameters - check if values are correct before using them
- Preserve query parameter state - update only the parameters that change
- 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. 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: