React Router: Client-Side Routing
Introduction to React Router
React Router is the standard routing library for React applications. It enables navigation between different views of your application, manages browser history, and keeps the UI in sync with the URL—all without page reloads.
Note: This lesson covers React Router v6, which introduced significant changes from v5. Make sure you're installing the correct version: npm install react-router-dom@6
Setting Up React Router
First, install React Router in your project:
# Install React Router
npm install react-router-dom
# Or with Yarn
yarn add react-router-dom
The core setup involves wrapping your application with BrowserRouter and defining routes:
// src/main.jsx or src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
Defining Routes with Routes and Route
In React Router v6, use the Routes component to wrap all your Route components:
// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
function App() {
return (
<div className="app">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}
export default App;
Tip: The path="*" route acts as a catch-all for unmatched URLs, perfect for 404 pages. It should always be the last route.
Navigation with Link and NavLink
React Router provides Link and NavLink components for navigation without full page reloads:
// src/components/Navigation.jsx
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* Basic Link - no styling based on active state */}
<Link to="/">Home</Link>
<Link to="/about">About</Link>
{/* NavLink - receives "active" class when route matches */}
<NavLink
to="/"
className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
>
Home
</NavLink>
<NavLink
to="/about"
style={({ isActive }) => ({
color: isActive ? '#ff6b6b' : '#333',
fontWeight: isActive ? 'bold' : 'normal'
})}
>
About
</NavLink>
</nav>
);
}
export default Navigation;
Key Difference: Link is for basic navigation, while NavLink provides active state styling, making it ideal for navigation menus.
Nested Routes
React Router v6 makes nested routing more intuitive with the Outlet component:
// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Profile from './pages/dashboard/Profile';
import Settings from './pages/dashboard/Settings';
import Analytics from './pages/dashboard/Analytics';
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
{/* Nested routes under /dashboard */}
<Route path="dashboard" element={<Dashboard />}>
<Route index element={<Profile />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
<Route path="analytics" element={<Analytics />} />
</Route>
</Route>
</Routes>
);
}
// src/components/Layout.jsx
import { Outlet } from 'react-router-dom';
import Navigation from './Navigation';
import Footer from './Footer';
function Layout() {
return (
<div>
<Navigation />
<main>
<Outlet /> {/* Child routes render here */}
</main>
<Footer />
</div>
);
}
// src/pages/Dashboard.jsx
import { Outlet, Link } from 'react-router-dom';
function Dashboard() {
return (
<div className="dashboard">
<aside>
<Link to="/dashboard/profile">Profile</Link>
<Link to="/dashboard/settings">Settings</Link>
<Link to="/dashboard/analytics">Analytics</Link>
</aside>
<section>
<Outlet /> {/* Nested dashboard routes render here */}
</section>
</div>
);
}
URL Parameters
Dynamic routes use URL parameters to pass data through the URL:
// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import UserProfile from './pages/UserProfile';
import BlogPost from './pages/BlogPost';
function App() {
return (
<Routes>
{/* Single parameter */}
<Route path="/users/:userId" element={<UserProfile />} />
{/* Multiple parameters */}
<Route path="/blog/:category/:postId" element={<BlogPost />} />
{/* Optional parameters with ? */}
<Route path="/products/:productId/:variant?" element={<Product />} />
</Routes>
);
}
// src/pages/UserProfile.jsx
import { useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
function UserProfile() {
const { userId } = useParams();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch user data based on userId
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading user...</div>
if (!user) return <div>User not found</div>
return (
<div>
<h1>{user.name}'s Profile</h1>
<p>User ID: {userId}</p>
<p>Email: {user.email}</p>
</div>
);
}
// src/pages/BlogPost.jsx
import { useParams } from 'react-router-dom';
function BlogPost() {
const { category, postId } = useParams();
return (
<div>
<h1>Blog Post</h1>
<p>Category: {category}</p>
<p>Post ID: {postId}</p>
</div>
);
}
Warning: URL parameters are always strings. If you need numbers, convert them: const id = parseInt(params.userId, 10);
Index Routes
Index routes render when the parent route matches exactly:
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
{/* This renders at exactly "/" */}
<Route index element={<Home />} />
{/* This renders at "/about" */}
<Route path="about" element={<About />} />
<Route path="products" element={<ProductsLayout />}>
{/* This renders at exactly "/products" */}
<Route index element={<ProductsList />} />
{/* This renders at "/products/:id" */}
<Route path=":id" element={<ProductDetail />} />
</Route>
</Route>
</Routes>
);
}
Exercise 1: Basic Routing Setup
Task: Create a multi-page application with the following routes:
- Home page at
/ - About page at
/about - Services page at
/services - Contact page at
/contact - 404 page for unmatched routes
Add a navigation bar with NavLink components that highlight the active page.
Exercise 2: Nested Routes with Products
Task: Create a products section with nested routes:
/products- Shows a list of product categories/products/:categoryId- Shows products in that category/products/:categoryId/:productId- Shows product details
Use sample data and the useParams hook to display the correct information.
Exercise 3: User Dashboard
Task: Build a user dashboard with nested routes:
/dashboard- Dashboard layout with sidebar/dashboard/overview- Overview page (index route)/dashboard/orders- Orders list/dashboard/profile- User profile
Use Outlet to render nested content and create a sidebar with NavLink for navigation.
Summary
In this lesson, you learned the fundamentals of React Router v6:
- Setting up
BrowserRouterand defining routes withRoutesandRoute - Navigation with
LinkandNavLinkcomponents - Creating nested routes with
Outlet - Using URL parameters with
useParams - Implementing index routes for default content
- Building catch-all routes for 404 pages
In the next lesson, we'll explore advanced routing techniques including programmatic navigation, protected routes, and lazy loading.