React Router

Course 2 · Ch 5
React Router
Multiple "pages" inside a single-page app, navigated without ever reloading the browser

Project 4 used four pieces of React Router with a quick primer to get a search page and a detail page working. This chapter covers routing properly — including the two pieces that primer left out entirely: programmatic navigation, and sharing a layout (like a persistent sidebar or header) across several routes.

The Setup, Recapped

// npm install react-router-dom import { BrowserRouter, Routes, Route } from "react-router-dom"; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /> </Routes> </BrowserRouter> ); }

BrowserRouter wraps the whole app once, near the top. Routes looks through its Route children for the one whose path matches the current URL, and renders only that one's element.

Link vs NavLink

import { Link, NavLink } from "react-router-dom"; <Link to="/about">About</Link> <NavLink to="/about" className={({ isActive }) => (isActive ? "nav-link active" : "nav-link")} > About </NavLink>

Both navigate without a full page reload, exactly like Project 4's Link. NavLink is specifically for navigation menus — it knows whether its own to matches the current URL, and exposes that as isActive inside a function passed to className (or style), making "highlight the current page in the nav" straightforward without manually comparing the URL yourself.

useParams and useNavigate

import { useParams, useNavigate } from "react-router-dom"; function ProductDetailPage() { const { id } = useParams(); const navigate = useNavigate(); function handleDelete() { // after some action completes, send the user somewhere else navigate("/products"); } return ( <div> <p>Showing product {id}</p> <button onClick={handleDelete}>Delete and go back</button> </div> ); }

useParams() was already used in Project 4 to read a dynamic URL segment (/product/:id{ id }). useNavigate() is new here — it returns a function for navigating programmatically, from inside an event handler or effect, rather than from a Link the user clicks. This is the right tool for redirecting after a form submits, after a delete completes, or after a login succeeds.

Nested Routes and a Shared Layout

import { Outlet } from "react-router-dom"; function DashboardLayout() { return ( <div> <Sidebar /> <main> <Outlet /> {/* the matched child route renders here */} </main> </div> ); } // in the route definitions: <Route path="/dashboard" element={<DashboardLayout />}> <Route path="profile" element={<ProfilePage />} /> <Route path="settings" element={<SettingsPage />} /> </Route>

Nesting Route elements means the parent's element (DashboardLayout, with its sidebar) renders for every URL starting with /dashboard, while <Outlet /> marks exactly where the matching child route's content should appear inside it. Visiting /dashboard/profile renders DashboardLayout with ProfilePage dropped into the Outlet — the sidebar never unmounts or re-renders just because the inner page changed, which is exactly the composition pattern (children/Outlet as a flexible slot) from Fundamentals Chapter 9, applied to routing.

A Catch-All "Not Found" Route

<Route path="*" element={<NotFoundPage />} />

A Route with path="*", placed last among the route definitions, matches any URL that didn't match an earlier, more specific route — the standard way to show a friendly 404 page instead of a blank screen for a mistyped or stale URL.

A plain <a href> defeats the entire point
Using a regular <a href="/about"> instead of <Link to="/about"> triggers a real full-page browser navigation — reloading the entire app from scratch, losing any in-memory state (cart contents, form data, anything not in localStorage), exactly the cost React Router exists to avoid. Link/NavLink intercept the click and update the URL without a reload; a plain anchor tag never does.

Coding Challenges

Challenge 1

Build a 3-page app (Home, About, Contact) with a nav bar using NavLink for each, styling the currently active link differently from the others.

📄 View solution
Challenge 2

Build a hardcoded list of products with Link to /product/:id for each, and a ProductDetailPage that reads the id via useParams and shows the matching product's details.

📄 View solution
Challenge 3

Build a layout route with a persistent sidebar (using Outlet) wrapping two nested child pages, plus a catch-all "*" route rendering a NotFoundPage for any unmatched URL.

📄 View solution

Chapter 5 Quick Reference

  • BrowserRouter wraps the app; Routes/Route map a path to an element
  • Link — basic navigation; NavLink — adds active-state awareness for nav menus
  • useParams() — reads dynamic URL segments; useNavigate() — navigates programmatically
  • Nested routes + <Outlet /> — a shared layout (sidebar, header) wrapping several child pages
  • path="*", placed last — catches any URL nothing else matched, for a 404 page
  • Always use Link/NavLink, never a plain <a href>, for in-app navigation
  • Next chapter: fetching data — proper loading/error patterns beyond the basics from Fundamentals Ch 8