Proper URL Parameter Handling in React Router
When working with React Router, there are specific patterns you should follow for passing and accessing URL parameters correctly. Here’s how to handle URL parameters properly in your React applications.
The Wrong Ways (Common Mistakes)
1. Manual String Concatenation (Fragile)
// ❌ Bad - manual string building
<Link to={`/user/${userId}`}>Profile</Link>
// ❌ Bad - direct string manipulation
navigate(`/products/${productId}`);
2. Query Strings Without Encoding
// ❌ Bad - unsafe query strings
<Link to={`/search?q=${searchTerm}`}>Search</Link>
3. Incorrect Route Definitions
// ❌ Bad - won't capture params
<Route path="/user/:id" element={<User />} />
// Then trying to use:
<Link to="/user/123?details=true">User</Link> // Params won't work
The Right Ways (Recommended Patterns)
1. Using React Router’s Param Syntax
// ✅ Good - proper route definition
<Routes>
<Route path="/user/:userId" element={<UserProfile />} />
<Route path="/products/:category/:id" element={<ProductPage />} />
</Routes>
// ✅ Good - generating links
<Link to="/user/123">Profile</Link>
<Link to={`/user/${userId}`}>Profile</Link> // Safe if userId is clean
2. Accessing Parameters in Components
import { useParams } from 'react-router-dom';
function UserProfile() {
// ✅ Good - accessing route params
const { userId } = useParams();
return <div>User ID: {userId}</div>;
}
3. Passing Multiple Parameters
// Route definition:
<Route path="/products/:category/:id" element={<ProductDetail />} />
// Generating the link:
<Link to="/products/books/123">View Product</Link>
// Accessing in component:
function ProductDetail() {
const { category, id } = useParams();
// ...
}
4. Handling Query Parameters
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
// ✅ Good - query parameter handling
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q');
const handleSearch = (term) => {
setSearchParams({ q: term });
};
return (
<div>
<input
value={query || ''}
onChange={(e) => handleSearch(e.target.value)}
/>
</div>
);
}
Best Practices
- Encode dynamic values: Use
encodeURIComponent()
for user-generated content
<Link to={`/search?q=${encodeURIComponent(userInput)}`}>Search</Link>
- Use TypeScript for type-safe parameters:
type ProductParams = {
category: string;
id: string;
};
function ProductDetail() {
const { category, id } = useParams<ProductParams>();
}
- Validate parameters before using them:
const { id } = useParams();
if (!id) return <NotFound />;
- For complex state, consider:
- Storing in global state (Redux, Context)
- Using the state prop in
navigate()
:jsx navigate('/checkout', { state: { from: 'cart' } });
Common Pitfalls to Avoid
- Nested parameters:
// ❌ Confusing
<Route path="/:a/:b/:c" />
// ✅ Clear naming
<Route path="/:category/:productId/:variant" />
- Optional parameters (React Router v6 doesn’t support this syntax directly):
// ❌ Won't work as expected
<Route path="/products/:id?" />
// ✅ Use separate routes
<Route path="/products" element={<ProductList />} />
<Route path="/products/:id" element={<ProductDetail />} />
- Special characters in paths – always encode them:
// ❌ Unsafe
<Link to={`/search/${userInput}`}>
// ✅ Safe
<Link to={`/search/${encodeURIComponent(userInput)}`}>