React Router Query Parameters
Introduction
Query parameters are a powerful way to pass data between pages in your React application. They appear in the URL after a question mark (?
) and take the form of key-value pairs like ?name=value&search=react
. When building interactive web applications, query parameters can help you maintain state in the URL, making your application more shareable and bookmarkable.
In this tutorial, you'll learn how to:
- Understand what query parameters are and why they're useful
- Read query parameters from the URL in React Router
- Update query parameters programmatically
- Handle multiple query parameters
- Build practical components that leverage query parameters
Understanding Query Parameters
Query parameters are additional pieces of information added to the end of a URL. They allow you to pass data to a page without changing the route itself.
For example, consider these URLs:
https://myapp.com/products
https://myapp.com/products?category=electronics
https://myapp.com/products?category=electronics&sort=price-asc
The first URL points to the products page with no additional parameters. The second URL includes a category
parameter with a value of "electronics". The third URL has two parameters: category
and sort
.
Why Use Query Parameters?
Query parameters are useful for:
- Filtering content - Such as displaying products from a specific category
- Sorting data - Like arranging products by price or date
- Pagination - Keeping track of what page the user is on
- Storing search terms - Remembering what a user searched for
- Preserving application state - Making URLs shareable with the same view
Reading Query Parameters with React Router
React Router (v6+) provides a hook called useSearchParams
that allows you to read and update query parameters. Let's see how to use it:
import { useSearchParams } from 'react-router-dom';
function ProductsPage() {
const [searchParams] = useSearchParams();
// Get the value of the 'category' parameter
// If it doesn't exist, default to 'all'
const category = searchParams.get('category') || 'all';
return (
<div>
<h1>Products</h1>
<p>Current category: {category}</p>
{/* Render products based on the category */}
</div>
);
}
In this example, if the URL is /products?category=electronics
, the category
variable will contain the string "electronics".
Updating Query Parameters
The useSearchParams
hook returns a function as its second element that you can use to update the query parameters:
import { useSearchParams } from 'react-router-dom';
function ProductsPage() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') || 'all';
const handleCategoryChange = (newCategory) => {
// Update the 'category' parameter
setSearchParams({ category: newCategory });
};
return (
<div>
<h1>Products</h1>
<p>Current category: {category}</p>
<div>
<button onClick={() => handleCategoryChange('electronics')}>
Electronics
</button>
<button onClick={() => handleCategoryChange('clothing')}>
Clothing
</button>
<button onClick={() => handleCategoryChange('books')}>
Books
</button>
</div>
{/* Render products based on the category */}
</div>
);
}
When you click one of the buttons, the URL will update to reflect the new category parameter, for example: /products?category=electronics
.
Preserving Existing Parameters
By default, setSearchParams
replaces all existing parameters. If you want to preserve other parameters, you need to include them in your update:
import { useSearchParams } from 'react-router-dom';
function ProductsPage() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') || 'all';
const sort = searchParams.get('sort') || 'default';
const handleCategoryChange = (newCategory) => {
// Create a new object with all current parameters
const newParams = {
category: newCategory,
sort: searchParams.get('sort') || 'default'
};
setSearchParams(newParams);
};
const handleSortChange = (newSort) => {
setSearchParams({
category: searchParams.get('category') || 'all',
sort: newSort
});
};
return (
<div>
<h1>Products</h1>
<div>
<label>Category:</label>
<select
value={category}
onChange={(e) => handleCategoryChange(e.target.value)}
>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="books">Books</option>
</select>
</div>
<div>
<label>Sort by:</label>
<select
value={sort}
onChange={(e) => handleSortChange(e.target.value)}
>
<option value="default">Default</option>
<option value="price-asc">Price (Low to High)</option>
<option value="price-desc">Price (High to Low)</option>
<option value="name">Name</option>
</select>
</div>
{/* Display products filtered by category and sorted accordingly */}
</div>
);
}
A more elegant way to preserve all existing parameters is to convert the current searchParams
to an object first:
import { useSearchParams } from 'react-router-dom';
function ProductsPage() {
const [searchParams, setSearchParams] = useSearchParams();
// Helper function to convert searchParams to regular object
const getSearchParamsObject = () => {
const params = {};
searchParams.forEach((value, key) => {
params[key] = value;
});
return params;
};
const handleCategoryChange = (newCategory) => {
const params = getSearchParamsObject();
setSearchParams({
...params,
category: newCategory
});
};
// Rest of the component...
}
Practical Example: Building a Filterable Product List
Let's build a practical example that uses query parameters to filter and sort a list of products:
import { useSearchParams } from 'react-router-dom';
import { useState, useEffect } from 'react';
// Mock product data
const allProducts = [
{ id: 1, name: 'Laptop', category: 'electronics', price: 999 },
{ id: 2, name: 'Smartphone', category: 'electronics', price: 699 },
{ id: 3, name: 'T-shirt', category: 'clothing', price: 29 },
{ id: 4, name: 'Jeans', category: 'clothing', price: 59 },
{ id: 5, name: 'JavaScript Book', category: 'books', price: 39 },
{ id: 6, name: 'React Cookbook', category: 'books', price: 49 },
];
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const [filteredProducts, setFilteredProducts] = useState(allProducts);
// Get parameters from URL
const category = searchParams.get('category') || 'all';
const sort = searchParams.get('sort') || 'default';
const page = parseInt(searchParams.get('page') || '1', 10);
// Update parameters
const updateParams = (updates) => {
const current = {};
// Convert current parameters to object
searchParams.forEach((value, key) => {
current[key] = value;
});
// Apply updates
setSearchParams({ ...current, ...updates });
};
// Filter and sort products when parameters change
useEffect(() => {
let result = [...allProducts];
// Apply category filter
if (category !== 'all') {
result = result.filter(product => product.category === category);
}
// Apply sorting
if (sort === 'price-asc') {
result.sort((a, b) => a.price - b.price);
} else if (sort === 'price-desc') {
result.sort((a, b) => b.price - a.price);
} else if (sort === 'name') {
result.sort((a, b) => a.name.localeCompare(b.name));
}
setFilteredProducts(result);
}, [category, sort]);
return (
<div>
<h1>Products</h1>
<div className="filters">
<div>
<label>Category: </label>
<select
value={category}
onChange={(e) => updateParams({ category: e.target.value, page: '1' })}
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="books">Books</option>
</select>
</div>
<div>
<label>Sort by: </label>
<select
value={sort}
onChange={(e) => updateParams({ sort: e.target.value })}
>
<option value="default">Default</option>
<option value="price-asc">Price (Low to High)</option>
<option value="price-desc">Price (High to Low)</option>
<option value="name">Name</option>
</select>
</div>
</div>
<div className="product-list">
{filteredProducts.map(product => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
<p>Category: {product.category}</p>
<p>Price: ${product.price}</p>
</div>
))}
</div>
{filteredProducts.length === 0 && (
<p>No products found matching the selected criteria.</p>
)}
</div>
);
}
export default ProductList;
Query Parameters vs. Route Parameters
It's important to understand the difference between query parameters and route parameters:
- Route Parameters (like
/users/:id
): Used for identifying a specific resource or page - Query Parameters (like
?category=books
): Used for filtering, sorting, or configuring the content of a page
Advanced Usage: Search Page Example
Let's build a search page that demonstrates some advanced query parameter techniques:
import { useSearchParams, useNavigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
// Get parameters from URL with defaults
const query = searchParams.get('q') || '';
const filter = searchParams.get('filter') || 'all';
const page = parseInt(searchParams.get('page') || '1', 10);
// Local state for the search input
const [searchInput, setSearchInput] = useState(query);
// Mock search results
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
// Handle search form submission
const handleSubmit = (e) => {
e.preventDefault();
// Only update URL if search term is not empty
if (searchInput.trim()) {
setSearchParams({
q: searchInput,
filter,
page: '1' // Reset to page 1 on new search
});
} else {
// If search is empty, remove the query parameter and reset to page 1
const newParams = { page: '1', filter };
setSearchParams(newParams);
}
};
// Simulate search API call
useEffect(() => {
const performSearch = async () => {
if (!query) {
setResults([]);
return;
}
setLoading(true);
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Simulate search results
const mockResults = [
{ id: 1, title: 'React Router Introduction', type: 'article' },
{ id: 2, title: 'Working with React Query', type: 'video' },
{ id: 3, title: 'React Router Parameters Guide', type: 'article' },
{ id: 4, title: 'URL Parameters in React', type: 'video' },
];
// Filter by type if needed
const filtered = filter === 'all'
? mockResults
: mockResults.filter(item => item.type === filter);
setResults(filtered);
setLoading(false);
};
performSearch();
}, [query, filter, page]);
return (
<div className="search-page">
<h1>Search Documentation</h1>
<form onSubmit={handleSubmit} className="search-form">
<input
type="text"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
placeholder="Search for articles, videos, etc."
className="search-input"
/>
<button type="submit" className="search-button">Search</button>
</form>
<div className="filters">
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => setSearchParams({ q: query, filter: 'all', page: '1' })}
>
All Results
</button>
<button
className={filter === 'article' ? 'active' : ''}
onClick={() => setSearchParams({ q: query, filter: 'article', page: '1' })}
>
Articles
</button>
<button
className={filter === 'video' ? 'active' : ''}
onClick={() => setSearchParams({ q: query, filter: 'video', page: '1' })}
>
Videos
</button>
</div>
{loading ? (
<p>Loading results...</p>
) : (
<div className="search-results">
{results.length > 0 ? (
results.map(result => (
<div key={result.id} className="result-card">
<h3>{result.title}</h3>
<span className="result-type">{result.type}</span>
</div>
))
) : (
query ? (
<p>No results found for "{query}".</p>
) : (
<p>Enter a search term to find results.</p>
)
)}
</div>
)}
</div>
);
}
export default SearchPage;
Best Practices for Working with Query Parameters
Here are some best practices to follow when working with query parameters:
-
Use query parameters for the right purposes:
- Filtering, sorting, pagination, search terms
- Non-critical options that change how data is displayed
-
Keep URLs clean and readable:
- Use descriptive parameter names (
category
instead ofc
) - Use consistent naming conventions
- Use descriptive parameter names (
-
Set sensible defaults:
- Always handle cases when parameters are missing
- Use
searchParams.get('param') || defaultValue
-
Update multiple parameters correctly:
- Remember to preserve existing parameters when updating
-
Reset dependent parameters when appropriate:
- For example, reset the page parameter to 1 when changing filters
-
Consider URL encoding for special characters:
encodeURIComponent()
for parameter values with spaces or special characters
Summary
Query parameters are a powerful way to create interactive and dynamic React applications while keeping important state in the URL. In this tutorial, you've learned:
- How to read query parameters using React Router's
useSearchParams
hook - How to update query parameters programmatically
- How to handle multiple parameters and preserve existing ones
- How to build practical components like filtered product lists and search pages
With these skills, you can create more user-friendly and shareable applications that maintain their state in the URL, allowing users to bookmark specific views or share them with others.
Further Exercises
To practice your skills with query parameters, try these exercises:
- Create a photo gallery with parameters for filtering by album, tag, and sorting options
- Build a blog list with parameters for filtering by category, author, and date range
- Implement a pagination system using query parameters that shows a limited number of items per page
- Create a movie database interface with filtering by genre, year, and rating
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)