Learn how to add navigation and multiple pages to your React applications using React Router for seamless single-page app experiences.
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.
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:
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!
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:
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:
/, /blog, /about, /contactReal 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:
:postId in the route captures dynamic valuesNow URLs like /blog/react-basics will show the specific post!
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:
/dashboard shows overview)/dashboard/analytics, /dashboard/settings, etc.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:
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:
/blog?q=react&category=tutorialsHandle 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>
);
}
Create a product catalog with routing:
Requirements:
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
}
// โ
Good - clear hierarchy
/blog
/blog/:postId
/dashboard
/dashboard/analytics
/dashboard/settings
// โ Bad - inconsistent structure
/blog
/post/:id
/user-dashboard
/analytics-page
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>;
}
function Navigation() {
const location = useLocation();
return (
<nav>
<Link
to="/blog"
className={location.pathname.startsWith('/blog') ? 'active' : ''}
>
Blog
</Link>
</nav>
);
}
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>
);
}
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
Test your React Router knowledge:
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
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!