Back to Tutorial
Routing & Navigation
Hands-on Practice
50 min

React Router

Learn how to add navigation and multiple pages to your React applications using React Router for seamless single-page app experiences.

Learning Objectives

  • Understand what client-side routing is and why it's needed
  • Set up React Router in your application
  • Create routes and navigate between pages
  • Handle URL parameters and query strings
  • Build nested routes and layouts
  • Implement programmatic navigation and history management

React Router

Imagine you're building a house. You could make it one giant room, but that would be impractical! Instead, you create different rooms - kitchen, bedroom, living room - and hallways to move between them. React Router does the same thing for your React apps, allowing you to create different "pages" and navigate between them seamlessly.

Without React Router, your entire app would be stuck on a single page. With it, you can build full-featured Single Page Applications (SPAs) that feel just like traditional multi-page websites, but with the speed and interactivity of React.

The Problem: Single Page Limitations

By default, React apps live on a single page. Everything happens in one URL:

// Without routing - everything in one component! function App() { const [currentPage, setCurrentPage] = useState('home'); return ( <div> <nav> <button onClick={() => setCurrentPage('home')}>Home</button> <button onClick={() => setCurrentPage('about')}>About</button> <button onClick={() => setCurrentPage('contact')}>Contact</button> </nav> {currentPage === 'home' && <HomePage />} {currentPage === 'about' && <AboutPage />} {currentPage === 'contact' && <ContactPage />} </div> ); }

Problems with this approach:

  • 🚫 URL doesn't change - users can't bookmark specific pages
  • 🚫 No browser back/forward - breaks user expectations
  • 🚫 Hard to share links - everything is just "mysite.com"
  • 🚫 No deep linking - can't send someone directly to a specific page
  • 🚫 SEO issues - search engines can't index different pages

What is React Router?

React Router is the standard library for adding routing to React applications. It enables:

Different URLs for different content (/home, /about, /contact)
Browser navigation (back/forward buttons work)
Bookmarkable pages (users can save and return to specific pages)
Deep linking (share links to specific content)
Nested routing (complex page structures)

Think of it as the "GPS system" for your React app!

Setting Up React Router

First, you need to install React Router DOM:

npm install react-router-dom

Then wrap your app with a router:

import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import HomePage from './components/HomePage'; import AboutPage from './components/AboutPage'; import ContactPage from './components/ContactPage'; function App() { return ( <BrowserRouter> <div> <nav> {/* We'll add navigation links here */} </nav> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/contact" element={<ContactPage />} /> </Routes> </div> </BrowserRouter> ); } export default App;

Key components:

  • BrowserRouter: Provides routing context to your entire app
  • Routes: Container for all your route definitions
  • Route: Defines a path and what component to show

Your First React Router App

Let's build a simple blog-style website to understand the basics:

import React from 'react'; import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; // Page Components function HomePage() { return ( <div style={{ padding: '20px' }}> <h1>🏠 Welcome to My Blog</h1> <p>This is the home page where we showcase featured articles.</p> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '20px', marginTop: '30px' }}> <div style={{ border: '1px solid #ddd', padding: '15px', borderRadius: '8px' }}> <h3>Getting Started with React</h3> <p>Learn the basics of React development...</p> <Link to="/blog/react-basics" style={{ color: '#0066cc' }}> Read more → </Link> </div> <div style={{ border: '1px solid #ddd', padding: '15px', borderRadius: '8px' }}> <h3>Understanding JavaScript</h3> <p>Master JavaScript fundamentals...</p> <Link to="/blog/javascript-guide" style={{ color: '#0066cc' }}> Read more → </Link> </div> </div> </div> ); } function AboutPage() { return ( <div style={{ padding: '20px' }}> <h1>📖 About This Blog</h1> <p>Welcome to my corner of the internet!</p> <div style={{ backgroundColor: '#f8f9fa', padding: '20px', borderRadius: '8px', marginTop: '20px' }}> <h2>My Story</h2> <p> I'm a passionate developer who loves sharing knowledge about web development, particularly React and modern JavaScript. </p> <h3>What You'll Find Here</h3> <ul> <li>🚀 React tutorials and tips</li> <li>💻 JavaScript best practices</li> <li>🎨 CSS and design insights</li> <li>🛠️ Development tools and workflows</li> </ul> </div> </div> ); } function BlogPage() { const posts = [ { id: 'react-basics', title: 'Getting Started with React', date: '2024-01-15' }, { id: 'javascript-guide', title: 'Understanding JavaScript', date: '2024-01-10' }, { id: 'css-tips', title: 'CSS Tips and Tricks', date: '2024-01-05' } ]; return ( <div style={{ padding: '20px' }}> <h1>📝 Blog Posts</h1> <p>All my articles about web development.</p> <div style={{ marginTop: '30px' }}> {posts.map(post => ( <article key={post.id} style={{ border: '1px solid #eee', padding: '20px', marginBottom: '20px', borderRadius: '8px' }}> <h2> <Link to={'/blog/' + post.id} style={{ color: '#333', textDecoration: 'none' }} > {post.title} </Link> </h2> <p style={{ color: '#666', fontSize: '14px' }}> Published on {new Date(post.date).toLocaleDateString()} </p> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Click to read the full article... </p> <Link to={'/blog/' + post.id} style={{ color: '#0066cc' }}> Read full article → </Link> </article> ))} </div> </div> ); } function ContactPage() { const [formData, setFormData] = React.useState({ name: '', email: '', message: '' }); const handleSubmit = (e) => { e.preventDefault(); alert('Message sent! (This is just a demo)'); setFormData({ name: '', email: '', message: '' }); }; const handleChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; return ( <div style={{ padding: '20px', maxWidth: '600px' }}> <h1>📧 Get In Touch</h1> <p>Have a question or want to collaborate? I'd love to hear from you!</p> <form onSubmit={handleSubmit} style={{ marginTop: '30px' }}> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}> Name </label> <input type="text" name="name" value={formData.name} onChange={handleChange} required style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px' }} /> </div> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}> Email </label> <input type="email" name="email" value={formData.email} onChange={handleChange} required style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px' }} /> </div> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}> Message </label> <textarea name="message" value={formData.message} onChange={handleChange} required rows={5} style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px', resize: 'vertical' }} /> </div> <button type="submit" style={{ backgroundColor: '#0066cc', color: 'white', padding: '12px 24px', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Send Message </button> </form> </div> ); } // Navigation Component function Navigation() { return ( <nav style={{ backgroundColor: '#333', padding: '1rem', marginBottom: '20px' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', maxWidth: '1200px', margin: '0 auto' }}> <Link to="/" style={{ color: 'white', textDecoration: 'none', fontSize: '24px', fontWeight: 'bold' }} > My Blog </Link> <div style={{ display: 'flex', gap: '20px' }}> <Link to="/" style={{ color: 'white', textDecoration: 'none' }} > Home </Link> <Link to="/blog" style={{ color: 'white', textDecoration: 'none' }} > Blog </Link> <Link to="/about" style={{ color: 'white', textDecoration: 'none' }} > About </Link> <Link to="/contact" style={{ color: 'white', textDecoration: 'none' }} > Contact </Link> </div> </div> </nav> ); } // Main App Component function App() { return ( <BrowserRouter> <div> <Navigation /> <div style={{ maxWidth: '1200px', margin: '0 auto' }}> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/blog" element={<BlogPage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/contact" element={<ContactPage />} /> </Routes> </div> </div> </BrowserRouter> ); } export default App;

Key concepts demonstrated:

  • Link component: Creates navigation links that update the URL
  • Routes and Route: Define which component shows for each URL
  • Navigation stays consistent: The nav bar appears on every page
  • Clean URLs: /, /blog, /about, /contact

URL Parameters and Dynamic Routes

Real apps often need dynamic routes based on data. Let's add individual blog post pages:

import { useParams } from 'react-router-dom'; // Blog post data (in real app, this would come from an API) const blogPosts = { 'react-basics': { title: 'Getting Started with React', date: '2024-01-15', content: ` React is a powerful JavaScript library for building user interfaces. In this comprehensive guide, we'll explore the fundamentals of React development and build your first interactive component. ## Why React? React makes it easy to create interactive UIs by breaking them down into reusable components. Each component manages its own state and renders efficiently when data changes. ## Your First Component Let's start with a simple example... ` }, 'javascript-guide': { title: 'Understanding JavaScript', date: '2024-01-10', content: ` JavaScript is the backbone of modern web development. This guide covers essential concepts every developer should master. ## ES6+ Features Modern JavaScript includes powerful features like arrow functions, destructuring, and modules that make code more readable and maintainable. ## Asynchronous Programming Learn about Promises, async/await, and how to handle asynchronous operations effectively... ` }, 'css-tips': { title: 'CSS Tips and Tricks', date: '2024-01-05', content: ` CSS is more powerful than ever! Let's explore modern techniques that will elevate your styling game. ## Flexbox and Grid Master modern layout systems that make responsive design intuitive and powerful. ## CSS Custom Properties Variables in CSS make theming and maintenance much easier... ` } }; function BlogPostPage() { const { postId } = useParams(); // Get the postId from the URL const post = blogPosts[postId]; // Handle case where post doesn't exist if (!post) { return ( <div style={{ padding: '20px', textAlign: 'center' }}> <h1>😕 Post Not Found</h1> <p>The blog post you're looking for doesn't exist.</p> <Link to="/blog" style={{ color: '#0066cc' }}> ← Back to Blog </Link> </div> ); } return ( <div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}> <nav style={{ marginBottom: '30px' }}> <Link to="/blog" style={{ color: '#0066cc' }}> ← Back to Blog </Link> </nav> <article> <header style={{ marginBottom: '30px' }}> <h1 style={{ fontSize: '2.5em', marginBottom: '10px' }}> {post.title} </h1> <p style={{ color: '#666', fontSize: '16px' }}> Published on {new Date(post.date).toLocaleDateString()} </p> </header> <div style={{ lineHeight: '1.6', fontSize: '18px' }}> {post.content.split('\n\n').map((paragraph, index) => { if (paragraph.startsWith('## ')) { return ( <h2 key={index} style={{ marginTop: '40px', marginBottom: '20px', fontSize: '1.5em' }}> {paragraph.replace('## ', '')} </h2> ); } return ( <p key={index} style={{ marginBottom: '20px' }}> {paragraph} </p> ); })} </div> </article> <footer style={{ marginTop: '50px', paddingTop: '30px', borderTop: '1px solid #eee' }}> <h3>Enjoyed this article?</h3> <p> <Link to="/contact" style={{ color: '#0066cc' }}> Get in touch </Link> to let me know what you'd like to read about next! </p> </footer> </div> ); } // Update the App component to include the new route function App() { return ( <BrowserRouter> <div> <Navigation /> <div style={{ maxWidth: '1200px', margin: '0 auto' }}> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/blog" element={<BlogPage />} /> <Route path="/blog/:postId" element={<BlogPostPage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/contact" element={<ContactPage />} /> </Routes> </div> </div> </BrowserRouter> ); }

New concepts:

  • URL Parameters: :postId in the route captures dynamic values
  • useParams hook: Extracts parameters from the current URL
  • Dynamic content: Same component shows different content based on URL
  • 404 handling: Show error message when content doesn't exist

Now URLs like /blog/react-basics will show the specific post!

Nested Routes and Layouts

For complex applications, you often need nested routes. Let's create a dashboard section:

import { Outlet, useLocation } from 'react-router-dom'; // Dashboard Layout Component function DashboardLayout() { const location = useLocation(); const isActive = (path) => { return location.pathname === path; }; return ( <div style={{ display: 'flex', minHeight: '80vh' }}> {/* Sidebar Navigation */} <nav style={{ width: '250px', backgroundColor: '#f8f9fa', padding: '20px', borderRight: '1px solid #ddd' }}> <h3 style={{ marginBottom: '20px' }}>Dashboard</h3> <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}> <Link to="/dashboard" style={{ padding: '10px 15px', textDecoration: 'none', borderRadius: '5px', backgroundColor: isActive('/dashboard') ? '#0066cc' : 'transparent', color: isActive('/dashboard') ? 'white' : '#333' }} > 📊 Overview </Link> <Link to="/dashboard/analytics" style={{ padding: '10px 15px', textDecoration: 'none', borderRadius: '5px', backgroundColor: isActive('/dashboard/analytics') ? '#0066cc' : 'transparent', color: isActive('/dashboard/analytics') ? 'white' : '#333' }} > 📈 Analytics </Link> <Link to="/dashboard/settings" style={{ padding: '10px 15px', textDecoration: 'none', borderRadius: '5px', backgroundColor: isActive('/dashboard/settings') ? '#0066cc' : 'transparent', color: isActive('/dashboard/settings') ? 'white' : '#333' }} > ⚙️ Settings </Link> <Link to="/dashboard/profile" style={{ padding: '10px 15px', textDecoration: 'none', borderRadius: '5px', backgroundColor: isActive('/dashboard/profile') ? '#0066cc' : 'transparent', color: isActive('/dashboard/profile') ? 'white' : '#333' }} > 👤 Profile </Link> </div> </nav> {/* Main Content Area */} <main style={{ flex: 1, padding: '20px' }}> <Outlet /> {/* This renders the nested route content */} </main> </div> ); } // Dashboard Page Components function DashboardOverview() { return ( <div> <h1>📊 Dashboard Overview</h1> <p>Welcome to your dashboard! Here's a summary of your account.</p> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '20px', marginTop: '30px' }}> <div style={{ backgroundColor: '#e3f2fd', padding: '20px', borderRadius: '8px', textAlign: 'center' }}> <h3>📝 Total Posts</h3> <p style={{ fontSize: '2em', margin: '10px 0' }}>23</p> </div> <div style={{ backgroundColor: '#f3e5f5', padding: '20px', borderRadius: '8px', textAlign: 'center' }}> <h3>👀 Page Views</h3> <p style={{ fontSize: '2em', margin: '10px 0' }}>1,234</p> </div> <div style={{ backgroundColor: '#e8f5e8', padding: '20px', borderRadius: '8px', textAlign: 'center' }}> <h3>💬 Comments</h3> <p style={{ fontSize: '2em', margin: '10px 0' }}>89</p> </div> </div> </div> ); } function DashboardAnalytics() { return ( <div> <h1>📈 Analytics</h1> <p>Detailed analytics about your blog performance.</p> <div style={{ marginTop: '30px' }}> <h3>Popular Posts</h3> <div style={{ marginTop: '15px' }}> {[ { title: 'Getting Started with React', views: 456 }, { title: 'Understanding JavaScript', views: 389 }, { title: 'CSS Tips and Tricks', views: 267 } ].map((post, index) => ( <div key={index} style={{ display: 'flex', justifyContent: 'space-between', padding: '10px', borderBottom: '1px solid #eee' }}> <span>{post.title}</span> <span style={{ color: '#666' }}>{post.views} views</span> </div> ))} </div> </div> </div> ); } function DashboardSettings() { const [settings, setSettings] = React.useState({ emailNotifications: true, darkMode: false, autoSave: true }); const handleToggle = (setting) => { setSettings(prev => ({ ...prev, [setting]: !prev[setting] })); }; return ( <div> <h1>⚙️ Settings</h1> <p>Customize your dashboard experience.</p> <div style={{ marginTop: '30px' }}> <h3>Preferences</h3> <div style={{ marginTop: '20px' }}> {Object.entries(settings).map(([key, value]) => ( <div key={key} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '15px 0', borderBottom: '1px solid #eee' }}> <span style={{ textTransform: 'capitalize' }}> {key.replace(/([A-Z])/g, ' $1').trim()} </span> <label style={{ cursor: 'pointer' }}> <input type="checkbox" checked={value} onChange={() => handleToggle(key)} style={{ marginRight: '10px' }} /> {value ? 'Enabled' : 'Disabled'} </label> </div> ))} </div> </div> </div> ); } function DashboardProfile() { const [profile, setProfile] = React.useState({ name: 'John Doe', email: 'john@example.com', bio: 'Web developer and blogger passionate about React and JavaScript.' }); const handleChange = (e) => { setProfile({ ...profile, [e.target.name]: e.target.value }); }; const handleSubmit = (e) => { e.preventDefault(); alert('Profile updated! (This is just a demo)'); }; return ( <div> <h1>👤 Profile</h1> <p>Manage your profile information.</p> <form onSubmit={handleSubmit} style={{ marginTop: '30px', maxWidth: '500px' }}> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}>Name</label> <input type="text" name="name" value={profile.name} onChange={handleChange} style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px' }} /> </div> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}>Email</label> <input type="email" name="email" value={profile.email} onChange={handleChange} style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px' }} /> </div> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}>Bio</label> <textarea name="bio" value={profile.bio} onChange={handleChange} rows={4} style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px', resize: 'vertical' }} /> </div> <button type="submit" style={{ backgroundColor: '#0066cc', color: 'white', padding: '12px 24px', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Update Profile </button> </form> </div> ); } // Update the main App component with nested routes function App() { return ( <BrowserRouter> <div> <Navigation /> <div style={{ maxWidth: '1200px', margin: '0 auto' }}> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/blog" element={<BlogPage />} /> <Route path="/blog/:postId" element={<BlogPostPage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/contact" element={<ContactPage />} /> {/* Nested Dashboard Routes */} <Route path="/dashboard" element={<DashboardLayout />}> <Route index element={<DashboardOverview />} /> <Route path="analytics" element={<DashboardAnalytics />} /> <Route path="settings" element={<DashboardSettings />} /> <Route path="profile" element={<DashboardProfile />} /> </Route> </Routes> </div> </div> </BrowserRouter> ); } // Don't forget to add Dashboard to the navigation! function Navigation() { return ( <nav style={{ backgroundColor: '#333', padding: '1rem', marginBottom: '20px' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', maxWidth: '1200px', margin: '0 auto' }}> <Link to="/" style={{ color: 'white', textDecoration: 'none', fontSize: '24px', fontWeight: 'bold' }} > My Blog </Link> <div style={{ display: 'flex', gap: '20px' }}> <Link to="/" style={{ color: 'white', textDecoration: 'none' }}> Home </Link> <Link to="/blog" style={{ color: 'white', textDecoration: 'none' }}> Blog </Link> <Link to="/dashboard" style={{ color: 'white', textDecoration: 'none' }}> Dashboard </Link> <Link to="/about" style={{ color: 'white', textDecoration: 'none' }}> About </Link> <Link to="/contact" style={{ color: 'white', textDecoration: 'none' }}> Contact </Link> </div> </div> </nav> ); }

Nested routing concepts:

  • Outlet component: Renders child route content inside parent layout
  • Index route: Default route for a parent (/dashboard shows overview)
  • Nested paths: /dashboard/analytics, /dashboard/settings, etc.
  • Layout sharing: Sidebar navigation appears on all dashboard pages

Programmatic Navigation

Sometimes you need to navigate from JavaScript code, not just user clicks:

import { useNavigate, useLocation } from 'react-router-dom'; function LoginForm() { const navigate = useNavigate(); const location = useLocation(); const [credentials, setCredentials] = React.useState({ username: '', password: '' }); const handleSubmit = async (e) => { e.preventDefault(); try { // Simulate login API call await new Promise(resolve => setTimeout(resolve, 1000)); if (credentials.username === 'admin' && credentials.password === 'password') { // Successful login - redirect to dashboard or intended page const redirectTo = location.state?.from || '/dashboard'; navigate(redirectTo, { replace: true }); } else { alert('Invalid credentials'); } } catch (error) { alert('Login failed'); } }; const handleChange = (e) => { setCredentials({ ...credentials, [e.target.name]: e.target.value }); }; return ( <div style={{ padding: '20px', maxWidth: '400px', margin: '50px auto' }}> <h1>🔐 Login</h1> <p>Please sign in to access your dashboard.</p> <form onSubmit={handleSubmit} style={{ marginTop: '30px' }}> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}> Username </label> <input type="text" name="username" value={credentials.username} onChange={handleChange} placeholder="Try 'admin'" style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px' }} /> </div> <div style={{ marginBottom: '20px' }}> <label style={{ display: 'block', marginBottom: '5px' }}> Password </label> <input type="password" name="password" value={credentials.password} onChange={handleChange} placeholder="Try 'password'" style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px' }} /> </div> <button type="submit" style={{ width: '100%', backgroundColor: '#0066cc', color: 'white', padding: '12px', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Login </button> </form> <div style={{ marginTop: '20px', textAlign: 'center' }}> <button onClick={() => navigate(-1)} style={{ background: 'none', border: 'none', color: '#0066cc', cursor: 'pointer', textDecoration: 'underline' }} > ← Go Back </button> </div> </div> ); } // Example of a component that redirects after an action function SuccessPage() { const navigate = useNavigate(); React.useEffect(() => { // Redirect to home page after 3 seconds const timer = setTimeout(() => { navigate('/', { replace: true }); }, 3000); return () => clearTimeout(timer); }, [navigate]); return ( <div style={{ padding: '20px', textAlign: 'center', marginTop: '50px' }}> <h1>✅ Success!</h1> <p>Your action was completed successfully.</p> <p>Redirecting to home page in 3 seconds...</p> <button onClick={() => navigate('/')} style={{ marginTop: '20px', backgroundColor: '#0066cc', color: 'white', padding: '10px 20px', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Go Home Now </button> </div> ); }

useNavigate features:

  • navigate('/path'): Go to a specific route
  • navigate(-1): Go back one page (like browser back button)
  • navigate(1): Go forward one page
  • replace: true: Replace current history entry instead of adding new one

Query Parameters and Search

Handle URL query parameters for filtering and search:

import { useSearchParams } from 'react-router-dom'; function SearchableBlogPage() { const [searchParams, setSearchParams] = useSearchParams(); const query = searchParams.get('q') || ''; const category = searchParams.get('category') || 'all'; const allPosts = [ { id: 'react-basics', title: 'Getting Started with React', category: 'react', date: '2024-01-15' }, { id: 'javascript-guide', title: 'Understanding JavaScript', category: 'javascript', date: '2024-01-10' }, { id: 'css-tips', title: 'CSS Tips and Tricks', category: 'css', date: '2024-01-05' }, { id: 'react-hooks', title: 'Mastering React Hooks', category: 'react', date: '2024-01-20' }, { id: 'es6-features', title: 'Modern JavaScript Features', category: 'javascript', date: '2024-01-12' } ]; // Filter posts based on search and category const filteredPosts = allPosts.filter(post => { const matchesQuery = query === '' || post.title.toLowerCase().includes(query.toLowerCase()); const matchesCategory = category === 'all' || post.category === category; return matchesQuery && matchesCategory; }); const handleSearch = (e) => { const newQuery = e.target.value; setSearchParams(prev => { if (newQuery) { prev.set('q', newQuery); } else { prev.delete('q'); } return prev; }); }; const handleCategoryChange = (newCategory) => { setSearchParams(prev => { if (newCategory !== 'all') { prev.set('category', newCategory); } else { prev.delete('category'); } return prev; }); }; return ( <div style={{ padding: '20px' }}> <h1>📝 Searchable Blog</h1> {/* Search and Filter Controls */} <div style={{ marginBottom: '30px', padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}> <div style={{ marginBottom: '15px' }}> <input type="text" placeholder="Search posts..." value={query} onChange={handleSearch} style={{ width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '16px' }} /> </div> <div> <label style={{ marginRight: '15px' }}>Category:</label> {['all', 'react', 'javascript', 'css'].map(cat => ( <button key={cat} onClick={() => handleCategoryChange(cat)} style={{ marginRight: '10px', padding: '8px 16px', border: '1px solid #ddd', borderRadius: '4px', backgroundColor: category === cat ? '#0066cc' : 'white', color: category === cat ? 'white' : '#333', cursor: 'pointer', textTransform: 'capitalize' }} > {cat} </button> ))} </div> </div> {/* Results */} <div> <p style={{ color: '#666', marginBottom: '20px' }}> {filteredPosts.length} post{filteredPosts.length !== 1 ? 's' : ''} found {query && ' for "' + query + '"'} {category !== 'all' && ' in ' + category} </p> {filteredPosts.length === 0 ? ( <div style={{ textAlign: 'center', padding: '40px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}> <h3>😔 No posts found</h3> <p>Try adjusting your search or filter criteria.</p> </div> ) : ( <div> {filteredPosts.map(post => ( <article key={post.id} style={{ border: '1px solid #eee', padding: '20px', marginBottom: '20px', borderRadius: '8px' }}> <h2> <Link to={'/blog/' + post.id} style={{ color: '#333', textDecoration: 'none' }} > {post.title} </Link> </h2> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' }}> <span style={{ backgroundColor: '#e3f2fd', color: '#1976d2', padding: '4px 8px', borderRadius: '12px', fontSize: '12px', textTransform: 'uppercase' }}> {post.category} </span> <span style={{ color: '#666', fontSize: '14px' }}> {new Date(post.date).toLocaleDateString()} </span> </div> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p> <Link to={'/blog/' + post.id} style={{ color: '#0066cc' }}> Read more → </Link> </article> ))} </div> )} </div> </div> ); }

Query parameter features:

  • useSearchParams: Hook to read and modify URL query parameters
  • searchParams.get('key'): Get a specific parameter value
  • setSearchParams: Update query parameters (updates URL automatically)
  • Bookmarkable searches: URLs like /blog?q=react&category=tutorials

404 Pages and Error Handling

Handle routes that don't exist:

function NotFoundPage() { const navigate = useNavigate(); return ( <div style={{ textAlign: 'center', padding: '50px 20px', maxWidth: '600px', margin: '0 auto' }}> <h1 style={{ fontSize: '6em', margin: '0' }}>404</h1> <h2>Page Not Found</h2> <p style={{ fontSize: '18px', color: '#666', marginBottom: '30px' }}> Oops! The page you're looking for doesn't exist. It might have been moved, deleted, or you entered the wrong URL. </p> <div style={{ display: 'flex', gap: '15px', justifyContent: 'center' }}> <button onClick={() => navigate(-1)} style={{ padding: '12px 24px', backgroundColor: '#6c757d', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > ← Go Back </button> <Link to="/" style={{ padding: '12px 24px', backgroundColor: '#0066cc', color: 'white', textDecoration: 'none', borderRadius: '4px', display: 'inline-block' }} > 🏠 Go Home </Link> </div> <div style={{ marginTop: '40px' }}> <h3>Popular Pages</h3> <div style={{ display: 'flex', flexDirection: 'column', gap: '10px', alignItems: 'center' }}> <Link to="/blog" style={{ color: '#0066cc' }}>📝 Blog</Link> <Link to="/about" style={{ color: '#0066cc' }}>📖 About</Link> <Link to="/contact" style={{ color: '#0066cc' }}>📧 Contact</Link> </div> </div> </div> ); } // Update the App component to include the 404 route function App() { return ( <BrowserRouter> <div> <Navigation /> <div style={{ maxWidth: '1200px', margin: '0 auto' }}> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/blog" element={<SearchableBlogPage />} /> <Route path="/blog/:postId" element={<BlogPostPage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/contact" element={<ContactPage />} /> <Route path="/login" element={<LoginForm />} /> <Route path="/success" element={<SuccessPage />} /> <Route path="/dashboard" element={<DashboardLayout />}> <Route index element={<DashboardOverview />} /> <Route path="analytics" element={<DashboardAnalytics />} /> <Route path="settings" element={<DashboardSettings />} /> <Route path="profile" element={<DashboardProfile />} /> </Route> {/* Catch-all route for 404 */} <Route path="*" element={<NotFoundPage />} /> </Routes> </div> </div> </BrowserRouter> ); }

Practice Exercise: Build a Product Catalog

Create a product catalog with routing:

Requirements:

  1. Product list page with category filtering
  2. Individual product detail pages
  3. Shopping cart page
  4. Search functionality with URL parameters
  5. Breadcrumb navigation

Starter structure:

const products = [ { id: 1, name: 'Laptop', category: 'electronics', price: 999 }, { id: 2, name: 'T-Shirt', category: 'clothing', price: 29 }, // Add more products... ]; function ProductCatalog() { // Your implementation here // Use useSearchParams for filtering // Link to individual product pages } function ProductDetail() { // Use useParams to get product ID // Show product details // Add to cart functionality } function ShoppingCart() { // Show cart items // Update quantities // Checkout flow }

Router Best Practices

1. Organize Routes Logically

// ✅ Good - clear hierarchy /blog /blog/:postId /dashboard /dashboard/analytics /dashboard/settings // ❌ Bad - inconsistent structure /blog /post/:id /user-dashboard /analytics-page

2. Use Loading States

function BlogPostPage() { const { postId } = useParams(); const [post, setPost] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetchPost(postId).then(setPost).finally(() => setLoading(false)); }, [postId]); if (loading) return <div>Loading...</div>; return <div>{/* post content */}</div>; }

3. Handle Navigation States

function Navigation() { const location = useLocation(); return ( <nav> <Link to="/blog" className={location.pathname.startsWith('/blog') ? 'active' : ''} > Blog </Link> </nav> ); }

4. Use Breadcrumbs for Deep Navigation

function Breadcrumbs() { const location = useLocation(); const pathnames = location.pathname.split('/').filter(x => x); return ( <nav> <Link to="/">Home</Link> {pathnames.map((name, index) => { const routeTo = '/' + pathnames.slice(0, index + 1).join('/'); const isLast = index === pathnames.length - 1; return isLast ? ( <span key={name}> / {name}</span> ) : ( <span key={name}> {' / '} <Link to={routeTo}>{name}</Link> </span> ); })} </nav> ); }

What We've Learned

Congratulations! You now understand:

What React Router is and why it's essential for SPAs
Setting up routes with BrowserRouter, Routes, and Route
Navigation with Link components and programmatic navigation
URL parameters for dynamic content
Nested routes and layouts with Outlet
Query parameters for search and filtering
Error handling with 404 pages

Quick Recap Quiz

Test your React Router knowledge:

  1. What component do you use to create navigation links?
  2. How do you access URL parameters in a component?
  3. What's the difference between Link and useNavigate?
  4. How do you create nested routes?

Answers: 1) Link component, 2) useParams hook, 3) Link is for user clicks, useNavigate is for programmatic navigation, 4) Use Outlet in parent and nested Route definitions

What's Next?

In our next lesson, we'll learn about Protected Routes - how to add authentication and authorization to your React Router setup. You'll discover how to protect certain pages, redirect unauthorized users, and build secure navigation patterns.

React Router gives you the foundation, and protected routes add the security layer on top!