Topic 11 / 14
React Router — Multi-Page Apps
Client-Side Routing
A React app is one HTML page, but users expect URLs: /courses, /courses/django, shareable and bookmarkable. React Router swaps components based on the URL without reloading:
npm install react-router-domimport { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/courses" element={<CourseList />} />
<Route path="/courses/:slug" element={<CourseDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}Links — Never <a> Internally
import { Link, NavLink } from "react-router-dom";
<Link to="/courses">Courses</Link>
// NavLink knows when it's active — perfect for navbars
<NavLink
to="/courses"
className={({ isActive }) => (isActive ? "nav-link active" : "nav-link")}
>
Courses
</NavLink>A plain <a href> triggers a full page reload and throws away all state. Link intercepts the click and just changes the route.
URL Parameters
import { useParams } from "react-router-dom";
function CourseDetail() {
const { slug } = useParams(); // from /courses/:slug
const { data: course, isLoading } = useFetch(`/api/courses/${slug}`);
if (isLoading) return <Spinner />;
if (!course) return <NotFound />;
return <h1>{course.title}</h1>;
}Query strings have their own hook:
const [searchParams, setSearchParams] = useSearchParams();
const page = Number(searchParams.get("page") ?? 1);
setSearchParams({ page: page + 1 }); // updates ?page= in the URLState in the URL is shareable, bookmarkable, and survives refresh — prefer it over useState for filters, tabs, and pagination.
Nested Routes & Layouts
Share a navbar/footer across pages with a layout route and Outlet:
import { Outlet } from "react-router-dom";
function Layout() {
return (
<>
<Navbar />
<main><Outlet /></main> {/* the matched child renders here */}
<Footer />
</>
);
}
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/courses" element={<CourseList />} />
</Route>
</Routes>Programmatic Navigation & Protected Routes
import { useNavigate, Navigate } from "react-router-dom";
// after an action
const navigate = useNavigate();
async function handleLogin(form) {
await login(form);
navigate("/dashboard");
}
// gate a route on auth
function RequireAuth({ children }) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
return children;
}
<Route path="/dashboard" element={<RequireAuth><Dashboard /></RequireAuth>} />That’s the complete routing toolkit: declarative routes, params, layouts, redirects. (Next.js, in its own series, bakes routing into the filesystem — same concepts, different ergonomics.)