Real applications need routes that can handle dynamic data. Instead of creating separate routes for every user, product, or post, you can use dynamic routes with URL parameters. A single route like /users/:id can handle /users/1, /users/2, and any other user ID. You'll also learn about query strings for filtering and search. Let's build flexible, data-driven routes! šÆ
URL Parameters (Route Parameters)
URL parameters let you capture values from the URL:
Defining Dynamic Routes
import { Routes, Route } from 'react-router-dom'
function App() {
return (
<Routes>
{/* Static routes */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Dynamic route with parameter */}
<Route path="/users/:id" element={<UserDetail />} />
{/* :id is a parameter - can be any value */}
{/* Multiple parameters */}
<Route path="/posts/:year/:month/:slug" element={<BlogPost />} />
{/* Nested dynamic routes */}
<Route path="/users/:userId/posts/:postId" element={<UserPost />} />
</Routes>
)
}
// URL matching:
// /users/123 ā UserDetail with id=123
// /users/456 ā UserDetail with id=456
// /users/alice ā UserDetail with id=alice
// /posts/2024/12/hello ā BlogPost with year=2024, month=12, slug=hello
// Parameters start with : in the route pathAccessing Parameters with useParams
import { useParams } from 'react-router-dom'
function UserDetail() {
// Get parameters from URL
const { id } = useParams()
const [user, setUser] = React.useState(null)
const [loading, setLoading] = React.useState(true)
React.useEffect(() => {
// Fetch user data using the ID from URL
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => {
setUser(data)
setLoading(false)
})
}, [id]) // Re-fetch when ID changes
if (loading) return <div>Loading...</div>
if (!user) return <div>User not found</div>
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<p>User ID: {id}</p>
</div>
)
}
// Usage:
// URL: /users/123 ā id = "123"
// URL: /users/456 ā id = "456"
// useParams returns an object with all parameters as strings!Multiple Parameters
import { useParams } from 'react-router-dom'
// Route: /posts/:year/:month/:slug
function BlogPost() {
const { year, month, slug } = useParams()
React.useEffect(() => {
// Fetch post using all parameters
fetch(`/api/posts/${year}/${month}/${slug}`)
.then(res => res.json())
.then(data => setPost(data))
}, [year, month, slug])
return (
<div>
<h1>Blog Post</h1>
<p>Year: {year}</p>
<p>Month: {month}</p>
<p>Slug: {slug}</p>
</div>
)
}
// URL: /posts/2024/12/react-tutorial
// year = "2024"
// month = "12"
// slug = "react-tutorial"
// All parameters are strings - convert if needed:
const yearNum = parseInt(year, 10)
const monthNum = parseInt(month, 10)Important: Parameters Are Strings!
useParams always returns strings, even if the URL contains numbers.
URL: /users/123
Result: id = "123" (string, not number)
Convert to number when needed:const userId = parseInt(id, 10)const userId = Number(id)
Query Strings (Search Parameters)
Query strings add extra data to URLs after the ?:
Using useSearchParams
import { useSearchParams } from 'react-router-dom'
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams()
// Read query parameters
const category = searchParams.get('category') || 'all'
const sort = searchParams.get('sort') || 'name'
const page = searchParams.get('page') || '1'
const [products, setProducts] = React.useState([])
React.useEffect(() => {
// Fetch filtered products
fetch(`/api/products?category=${category}&sort=${sort}&page=${page}`)
.then(res => res.json())
.then(data => setProducts(data))
}, [category, sort, page])
// Update query parameters
const handleCategoryChange = (newCategory) => {
setSearchParams({ category: newCategory, sort, page })
}
const handleSortChange = (newSort) => {
setSearchParams({ category, sort: newSort, page })
}
return (
<div>
<h1>Products</h1>
{/* Filters */}
<select value={category} onChange={e => handleCategoryChange(e.target.value)}>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<select value={sort} onChange={e => handleSortChange(e.target.value)}>
<option value="name">Name</option>
<option value="price">Price</option>
<option value="date">Date</option>
</select>
{/* Product list */}
{products.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
)
}
// URL examples:
// /products?category=electronics&sort=price&page=2
// category = "electronics"
// sort = "price"
// page = "2"
// Benefits:
// - Shareable URLs with filters
// - Browser back/forward works with filters
// - Bookmarkable search resultsQuery String Patterns
import { useSearchParams, useNavigate } from 'react-router-dom'
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams()
const navigate = useNavigate()
// Reading parameters
const query = searchParams.get('q') // /search?q=react
const page = searchParams.get('page') // /search?page=2
const filter = searchParams.get('filter') // /search?filter=recent
// Check if parameter exists
const hasQuery = searchParams.has('q')
// Get all values (if multiple values for same key)
const tags = searchParams.getAll('tag') // /search?tag=react&tag=hooks
// Setting parameters (replaces all)
setSearchParams({ q: 'react', page: '1' })
// URL becomes: /search?q=react&page=1
// Updating parameters (preserves others)
setSearchParams(prev => {
prev.set('page', '2') // Update page
return prev
})
// Deleting parameters
setSearchParams(prev => {
prev.delete('filter') // Remove filter
return prev
})
// Multiple updates at once
const updateFilters = (newFilters) => {
setSearchParams(prev => {
Object.entries(newFilters).forEach(([key, value]) => {
if (value) {
prev.set(key, value)
} else {
prev.delete(key)
}
})
return prev
})
}
return <div>Search Page</div>
}
// Common patterns:
// - Pagination: ?page=2
// - Sorting: ?sort=name&order=asc
// - Filtering: ?category=electronics&price_min=100
// - Search: ?q=react+hooks
// - Multiple filters: ?tag=react&tag=hooks&tag=tutorialInteractive Demo
š” Tip: Edit the code above and click "Run" to see your changes
Optional Parameters
Create routes where parameters are optional:
import { Routes, Route, useParams } from 'react-router-dom'
function App() {
return (
<Routes>
{/* Both with and without parameter */}
<Route path="/blog" element={<BlogList />} />
<Route path="/blog/:slug" element={<BlogPost />} />
</Routes>
)
}
function BlogPost() {
const { slug } = useParams()
if (!slug) {
// This won't happen with this route structure,
// but useful for other patterns
return <div>No post selected</div>
}
return <div>Post: {slug}</div>
}
// Alternative: Use query string for optional data
function App() {
return (
<Routes>
<Route path="/blog" element={<Blog />} />
</Routes>
)
}
function Blog() {
const [searchParams] = useSearchParams()
const slug = searchParams.get('post') // /blog?post=my-post
if (slug) {
return <BlogPost slug={slug} />
}
return <BlogList />
}
// URL parameters vs Query strings:
// Required data ā URL parameter (/users/:id)
// Optional data ā Query string (/search?q=react)Catch-All Segments
Match multiple path segments with the * wildcard:
import { Routes, Route, useParams } from 'react-router-dom'
function App() {
return (
<Routes>
{/* Catch-all route for docs */}
<Route path="/docs/*" element={<Documentation />} />
{/* 404 catch-all */}
<Route path="*" element={<NotFound />} />
</Routes>
)
}
function Documentation() {
const { '*': splat } = useParams()
// splat contains everything after /docs/
// /docs/getting-started/installation
// splat = "getting-started/installation"
return (
<div>
<h1>Documentation</h1>
<p>Section: {splat}</p>
</div>
)
}
// Use cases:
// - File browsers (/files/documents/2024/report.pdf)
// - Documentation (/docs/react/hooks/useState)
// - Deep linking (/app/workspace/project/task/subtask)Combining URL Patterns
Real apps often combine parameters and query strings:
import { useParams, useSearchParams } from 'react-router-dom'
// Route: /users/:userId/posts
function UserPosts() {
// Get route parameter
const { userId } = useParams()
// Get query parameters
const [searchParams] = useSearchParams()
const page = searchParams.get('page') || '1'
const sort = searchParams.get('sort') || 'date'
const filter = searchParams.get('filter') || 'all'
React.useEffect(() => {
// Fetch user's posts with filters
fetch(`/api/users/${userId}/posts?` + new URLSearchParams({
page,
sort,
filter
}))
.then(res => res.json())
.then(data => setPosts(data))
}, [userId, page, sort, filter])
return (
<div>
<h1>Posts by User {userId}</h1>
<p>Page {page} | Sort: {sort} | Filter: {filter}</p>
</div>
)
}
// Example URLs:
// /users/123/posts
// /users/123/posts?page=2
// /users/123/posts?page=2&sort=likes&filter=published
// Pattern:
// - Use route params for required resource IDs
// - Use query params for optional filtering/paginationNavigating with Parameters
import { useNavigate, Link } from 'react-router-dom'
function UserList({ users }) {
const navigate = useNavigate()
const handleUserClick = (userId) => {
// Navigate with URL parameter
navigate(`/users/${userId}`)
}
const handleSearch = (query) => {
// Navigate with query string
navigate(`/search?q=${encodeURIComponent(query)}`)
}
const handleFilteredView = (category, sort) => {
// Navigate with multiple query params
navigate(`/products?` + new URLSearchParams({
category,
sort,
page: '1'
}))
}
return (
<div>
{users.map(user => (
<div key={user.id}>
{/* Link with parameter */}
<Link to={`/users/${user.id}`}>
{user.name}
</Link>
{/* Button navigation */}
<button onClick={() => handleUserClick(user.id)}>
View Profile
</button>
</div>
))}
</div>
)
}
// Tips:
// - Use template literals for params
// - Use URLSearchParams for query strings
// - Use encodeURIComponent for special charactersDynamic Routes Best Practices
- Use URL params for required IDs - /users/:id
- Use query strings for optional filters - ?sort=name
- Always validate parameter types - Convert strings to numbers
- Handle missing/invalid params gracefully - Show 404 or redirect
- Keep URLs readable and SEO-friendly - Use slugs, not just IDs
- Encode special characters - Use encodeURIComponent
- Make URLs shareable - Include all necessary data
- Use consistent naming - /users/:userId not /users/:id
- Document your URL structure - For team reference
- Consider nested resources - /users/:userId/posts/:postId
- Avoid too many parameters - Maximum 3-4 per route
Common Pitfalls:
- Type errors: Remember params are strings!
- Not handling null: Check if param exists
- Special characters: Always encode/decode
- Route conflicts: /users/new vs /users/:id (put specific routes first)
- Missing validation: Check param format before use
Key Takeaways
- Dynamic routes use
:paramNamesyntax useParamsaccesses URL parameters- All parameters are strings - convert when needed
useSearchParamshandles query strings- URL params for required data (IDs, slugs)
- Query strings for optional data (filters, pagination)
- Multiple parameters supported:
/:year/:month/:slug - Catch-all routes with
*wildcard - Combine params and query strings for complex routes
- Always validate and handle invalid parameters
- Use
encodeURIComponentfor special characters - Keep URLs readable and shareable
- React Router v6 ranks routes by specificity
What's Next?
Congratulations! You've completed the React Router section. You now understand:
- Setting up client-side routing
- Creating routes and navigation
- Programmatic navigation with useNavigate
- Nested routes and layouts
- Dynamic routes with parameters
- Query strings for filtering
You can now build complete single-page applications with sophisticated routing! You know how to structure routes, handle navigation, work with dynamic data, and create shareable URLs.
You've now completed 37 out of 42 tutorials - over 88% of the React tutorial series! You have comprehensive knowledge of:
- JSX and component fundamentals
- State management and hooks
- Side effects and data fetching
- Performance optimization
- Advanced patterns (hooks, HOCs, render props)
- Complete routing with React Router
Only 5 tutorials remaining - you're almost at React mastery! š