Topic 8: React Router
📖 11 min read · 🎯 intermediate · 🧭 Prerequisites: building-a-http-server-with-node-js-using-http-apis, http
Why this matters
Up until now, every React app you've built probably lives on one page — click something, the content changes, but the URL stays the same. That feels fine at first. But here's the thing — if a user refreshes the page, they land back at the start. You can't share a link to a specific screen. There's no "back button" behaviour. Real apps don't work like that. react-router-dom fixes exactly this: it connects what React renders to the browser's URL, so each view gets its own address. Today we'll wire up BrowserRouter, Route, Switch, nested routes, useHistory for programmatic navigation, and protected routes with Redirect.
What You'll Learn
- Install and configure
react-router-domin a Create React App project - Define declarative routes with
BrowserRouter,Switch,Route, andLink - Build nested routes using
useRouteMatchanduseParams - Navigate programmatically with the
useHistoryhook - Protect routes with a
PrivateRoutecomponent that redirects unauthenticated users
The Analogy
Think of your React application as a large department store. Each floor is a distinct page — Home Goods on the ground floor, Electronics on the second, a members-only VIP lounge on the third. BrowserRouter is the building itself: it tracks which floor you're on. Route components are the floor directories that decide what to display. Link tags are the elevator buttons. Redirect is the security guard on the VIP floor who sends you back to the lobby if you don't have a membership card. Without this infrastructure, every customer would be stuck on the ground floor no matter what button they pressed.
Chapter 1: Introduction to React Router
React Router is the standard library for routing in React. It enables navigation among views of various components in a React application, allows changing the browser URL, and keeps the UI in sync with the URL — all without a full-page reload.
Key primitives from react-router-dom:
| Primitive | Role |
|---|---|
BrowserRouter | Wraps the app; manages history via the HTML5 History API |
Switch | Renders the first Route that matches the current URL |
Route | Declares a path-to-component mapping |
Link | Renders an <a> tag that updates the URL without reloading |
Redirect | Immediately sends the user to a different path |
useHistory | Hook that returns the history object for imperative navigation |
useParams | Hook that returns URL parameters from the current route |
useRouteMatch | Hook that returns match data for the current route |
Chapter 2: Setting Up React Router
the trainer had the class scaffold a fresh project and install the library.
npx create-react-app react-router-example
cd react-router-example
npm install react-router-dom
With that, react-router-dom is available — no extra configuration needed for Create React App.
Chapter 3: Basic Routing
The foundation of any routed app is a BrowserRouter at the top of the tree, a Switch to match routes exclusively, and Link components for navigation.
src/App.js — basic three-route setup:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</div>
</Router>
);
}
export default App;
The
exactprop on the/route is critical — without it, every URL matches/because every path starts with a slash, andSwitchwould stop there.
Now create the three page components:
src/components/Home.js:
import React from 'react';
function Home() {
return (
<div>
<h1>Home Page</h1>
<p>Welcome to the Home page!</p>
</div>
);
}
export default Home;
src/components/About.js:
import React from 'react';
function About() {
return (
<div>
<h1>About Page</h1>
<p>Learn more about us on this page.</p>
</div>
);
}
export default About;
src/components/Contact.js:
import React from 'react';
function Contact() {
return (
<div>
<h1>Contact Page</h1>
<p>Get in touch with us on this page.</p>
</div>
);
}
export default Contact;
At this point clicking the nav links swaps the rendered component without reloading the page — that's client-side routing in action.
Chapter 4: Nested Routing
Real applications have hierarchies: /topics, /topics/rendering, /topics/components. React Router handles this with nested routes, and the useRouteMatch and useParams hooks make it clean.
graph TD
A["/"] --> B[Home]
A --> C["/about"]
A --> D["/contact"]
A --> E["/topics"]
E --> F["/topics/rendering"]
E --> G["/topics/components"]
E --> H["/topics/props-v-state"]
src/components/Topics.js — parent route that renders its own sub-nav and a nested Route:
import React from 'react';
import { Route, Link, useRouteMatch } from 'react-router-dom';
import Topic from './Topic';
function Topics() {
let match = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Route path={`${match.path}/:topicId`} component={Topic} />
<Route exact path={match.path}>
<h3>Please select a topic.</h3>
</Route>
</div>
);
}
export default Topics;
src/components/Topic.js — reads the :topicId URL parameter via useParams:
import React from 'react';
import { useParams } from 'react-router-dom';
function Topic() {
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}
export default Topic;
Now update App.js to include the /topics route:
src/App.js — updated with Topics:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
import Topics from './components/Topics';
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route path="/topics" component={Topics} />
</Switch>
</div>
</Router>
);
}
export default App;
Notice that the Topics route does not use exact — it must match /topics AND /topics/:topicId, so partial matching is intentional.
Chapter 5: Programmatic Navigation
Sometimes you need to navigate in response to logic — a form submission, a login success, a timer — rather than a user clicking a link. The useHistory hook gives you the history object, and history.push('/path') does the job.
src/components/NavigateButton.js:
import React from 'react';
import { useHistory } from 'react-router-dom';
function NavigateButton() {
let history = useHistory();
function handleClick() {
history.push('/about');
}
return (
<button type="button" onClick={handleClick}>
Go to About
</button>
);
}
export default NavigateButton;
Integrate it into App.js by importing NavigateButton and placing it below the Switch:
src/App.js — with NavigateButton:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
import Topics from './components/Topics';
import NavigateButton from './components/NavigateButton';
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route path="/topics" component={Topics} />
</Switch>
<NavigateButton />
</div>
</Router>
);
}
export default App;
history.push adds a new entry to the browser history stack. history.replace swaps the current entry instead — useful after login when you don't want the user to hit Back and end up on the login screen again.
Chapter 6: Redirects and Navigation Guards
Access control is one of the most common routing requirements: if the user is not authenticated, redirect them away from protected pages. React Router's Redirect component makes this declarative.
src/components/PrivateRoute.js — a wrapper that checks auth before rendering:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
function PrivateRoute({ component: Component, ...rest }) {
const isAuthenticated = false; // Replace with actual authentication logic
return (
<Route
{...rest}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/" />
}
/>
);
}
export default PrivateRoute;
src/components/Secret.js — the protected page:
import React from 'react';
function Secret() {
return (
<div>
<h1>Secret Page</h1>
<p>This is a secret page only accessible to authenticated users.</p>
</div>
);
}
export default Secret;
src/App.js — final version with PrivateRoute and /secret:
import React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
import Topics from './components/Topics';
import PrivateRoute from './components/PrivateRoute';
import Secret from './components/Secret';
import NavigateButton from './components/NavigateButton';
function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
<li>
<Link to="/secret">Secret</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
<Route path="/topics" component={Topics} />
<PrivateRoute path="/secret" component={Secret} />
</Switch>
<NavigateButton />
</div>
</Router>
);
}
export default App;
With isAuthenticated set to false, visiting /secret immediately redirects back to /. Swap in real auth logic — a JWT check, a context value, a Redux selector — and the guard becomes production-ready.
🧪 Try It Yourself
Task: Extend the application with a user profile route that is only accessible when a userId URL parameter is present.
- Create
src/components/Profile.jsthat reads a:userIdparam withuseParamsand displays "Profile for user: {userId}". - Add a
<Route path="/profile/:userId" component={Profile} />inside theSwitchinApp.js. - Add a
<Link to="/profile/42">My Profile</Link>in the nav.
Success criterion: Clicking "My Profile" renders "Profile for user: 42" in the browser. Manually changing the URL to /profile/99 should render "Profile for user: 99" without a page reload.
Starter snippet for Profile.js:
import React from 'react';
import { useParams } from 'react-router-dom';
function Profile() {
let { userId } = useParams();
return (
<div>
<h1>Profile for user: {userId}</h1>
</div>
);
}
export default Profile;
🔍 Checkpoint Quiz
Q1. What is the purpose of the exact prop on <Route path="/" exact component={Home} />?
A) It makes the route case-sensitive
B) It prevents the route from matching any URL that merely starts with /
C) It disables nested routes under /
D) It forces a full page reload when navigating to /
Q2. Given this component:
function NavigateButton() {
let history = useHistory();
function handleClick() {
history.push('/about');
}
return <button onClick={handleClick}>Go</button>;
}
What happens when the user clicks "Go" and then immediately clicks the browser's Back button?
A) Nothing — history.push disables the Back button
B) The user returns to the page they were on before clicking "Go"
C) The app crashes because useHistory is not a valid hook
D) The browser reloads the page from the server
Q3. In the Topics component, useRouteMatch() returns an object with both url and path properties. Why are match.url used for Link to props while match.path is used for Route path props?
A) They are interchangeable — the distinction is just style
B) match.url is the actual matched URL string (safe for links), while match.path contains the pattern with parameter placeholders (required for route definitions)
C) match.path is deprecated in newer versions of React Router
D) match.url only works inside a Switch
Q4. You want to guard the /dashboard route so unauthenticated users are sent to /login instead of /. What change to PrivateRoute achieves this?
A) Replace <Redirect to="/" /> with <Redirect to="/login" />
B) Add exact to the inner <Route>
C) Use history.push('/login') instead of <Redirect>
D) Wrap PrivateRoute in another Switch
A1. B — Without exact, React Router's default path matching is prefix-based, so / matches every URL. exact requires the full path to equal /.
A2. B — history.push adds a new entry to the browser history stack, so the Back button navigates to the previous entry, just like normal browser navigation.
A3. B — match.url is the concrete matched string (e.g., /topics), which is correct for building <Link to> hrefs. match.path is the pattern (e.g., /topics), which preserves :param placeholders needed by <Route path> to match dynamic segments.
A4. A — The <Redirect to="/" /> inside PrivateRoute controls the destination. Changing to="/" to to="/login" redirects unauthenticated users to the login page instead of home.
🪞 Recap
react-router-domprovidesBrowserRouter,Switch,Route, andLinkas the four core building blocks of client-side routing in React.- The
exactprop on aRouteprevents prefix matching, which is essential for the root/path. - Nested routes are composed using
useRouteMatchfor relative path construction anduseParamsto read URL parameters. useHistoryexposeshistory.pushandhistory.replacefor programmatic navigation outside of click handlers onLinkcomponents.PrivateRoutewraps React Router'sRoutewith auth logic and usesRedirectto enforce access control declaratively.
📚 Further Reading
- React Router v5 Official Docs — the authoritative guide for the v5 API used in this lesson
- React Router: A Complete Guide — deep dive into advanced patterns including code splitting and scroll restoration
- useHistory vs useNavigate — comparison article for teams considering migration to React Router v6
- ⬅️ Previous: HTTP
- ➡️ Next: Using Libraries in PHP