Topic 8 of 15 · React Developer

Topic 8 : React Router

Lesson TL;DRTopic 8: React Router 📖 11 min read · 🎯 intermediate · 🧭 Prerequisites: events, reactlists Why this matters Up until now, every React app you've built lives on a single page — no matter what you cl...
11 min read·intermediate·react · react-router · routing · navigation

Topic 8: React Router

📖 11 min read · 🎯 intermediate · 🧭 Prerequisites: events, react-lists

Why this matters

Up until now, every React app you've built lives on a single page — no matter what you click, the URL never changes. That feels fine for small demos, but imagine handing someone a link to your app's "About" section and it just opens the homepage. Frustrating, right? React Router fixes exactly that. It's the library that lets you tie specific URLs — /home, /about, /products/42 — to specific components, so your app feels like a real multi-page website. That's what react-router-dom gives you, and that's what we're building today.

What You'll Learn

  • Install and configure react-router-dom in a React application
  • Build declarative routes using BrowserRouter, Route, Switch, and Link
  • Compose nested routes with useRouteMatch and useParams
  • Navigate programmatically with the useHistory hook
  • Protect routes with a PrivateRoute pattern and Redirect

The Analogy

Think of React Router as a city's postal system. Your app is the city, each component is a building, and every URL is a street address. The BrowserRouter is the postmaster who reads the current address bar; Route entries are the buildings registered at specific addresses; Link components are the signposts that tell citizens where to go next; and Switch is the routing algorithm that delivers mail to exactly one building rather than broadcasting to all of them. When you want to forward mail automatically — say, because a building is restricted — you use a Redirect, which is exactly like returning a letter stamped "Forward to: /" .

Chapter 1: Introduction to React Router

React Router is the standard library for routing in React applications. It enables navigation among views of various components, allows changing the browser URL, and — crucially — keeps the UI in sync with the URL at all times.

Key responsibilities React Router handles:

  • Matching the current URL to a component
  • Rendering that component into the page
  • Updating the URL without a full browser reload
  • Providing hooks so components can read route parameters and navigate programmatically

Chapter 2: Setting Up React Router

Start by scaffolding a fresh React app and installing react-router-dom.

npx create-react-app react-router-example
cd react-router-example
npm install react-router-dom

react-router-dom is the DOM-specific binding of React Router. It re-exports everything from react-router and adds browser-focused components like BrowserRouter and Link.

Chapter 3: Basic Routing

the trainer walked the class through the three primitives that underpin every React Router setup: BrowserRouter, Route, and Link.

PrimitiveRole
BrowserRouterWraps the app; listens to the browser history API
RouteDeclares which component renders at which path
SwitchRenders only the first Route that matches
LinkRenders an <a> tag that updates the URL without reload

src/App.js — wiring up three basic routes:

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;

Notice the exact prop on the "/" route. Without it, React Router would match / for every path (since all paths start with /), and Home would always render.

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;

Chapter 4: Nested Routing

Real applications need hierarchy — a /topics page that itself renders sub-routes like /topics/rendering or /topics/components. React Router handles this with the useRouteMatch hook, which returns the matched URL and path for the currently active route so child components can build relative links without hardcoding parent paths.

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 — the parent that owns its own nested Route declarations:

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;

match.url produces the actual matched URL string (used in Link hrefs), while match.path is the route pattern (used in Route path props). They look the same for static routes but differ when parameters are involved.

src/components/Topic.js — reads the dynamic segment with 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;

useParams returns an object whose keys match the :paramName segments defined in the parent route path. Here, :topicId becomes topicId.

src/App.js — updated to include the Topics route:

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;

Chapter 5: Programmatic Navigation

Sometimes navigation should be triggered by logic — a form submission, an API response, or a button click — rather than a <Link> click. The useHistory hook gives you direct access to the browser history object.

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;

history.push(path) adds a new entry to the browser history stack and navigates there. Use history.replace(path) instead when you don't want the user to be able to press the back button to return (e.g., after logging out).

src/App.js — integrating 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;

Chapter 6: Redirects and Navigation Guards

Not all routes should be publicly accessible. The PrivateRoute pattern wraps Route and inspects an authentication condition before deciding whether to render the target component or redirect the user elsewhere using React Router's Redirect component.

src/components/PrivateRoute.js:

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;

PrivateRoute accepts the same props as Route (including path and exact). It spreads ...rest onto the inner Route and uses the render prop — a function that receives route props — to conditionally render the component or issue a Redirect. When isAuthenticated is false, any attempt to visit the protected path instantly sends the user back to "/".

src/components/Secret.js — the protected component:

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 — the final version with PrivateRoute, Secret, and NavigateButton all wired in:

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;

🧪 Try It Yourself

Task: Add a user profile route that is only accessible when isAuthenticated is true, and test the redirect behavior by toggling the flag.

  1. Create src/components/Profile.js that renders a heading and a paragraph with any user info you like.
  2. Create a second PrivateRoute in App.js at path "/profile" pointing to Profile.
  3. Add a <Link to="/profile">Profile</Link> in the nav.
  4. Run the app with npm start.

With isAuthenticated = false: clicking "Profile" should immediately redirect you to "/".

With isAuthenticated = true: clicking "Profile" should render the Profile component.

Starter snippet for Profile.js:

import React from 'react';

function Profile() {
    return (
        <div>
            <h1>Profile Page</h1>
            <p>Welcome back, students.</p>
        </div>
    );
}

export default Profile;

Success criterion: toggling isAuthenticated between false and true in PrivateRoute.js should switch the /profile route between redirecting and rendering — without touching any other file.

🔍 Checkpoint Quiz

Q1. Why is the exact prop required on the "/" route when using Switch?

A) Because / is not a valid path without it
B) Because without it, "/" matches every path (all paths begin with /), so Home would always render
C) Because Switch ignores paths shorter than two characters by default
D) Because BrowserRouter requires exact on root routes

Q2. Given this component, what happens when a user visits /topics/hooks in the browser?

function Topics() {
    let match = useRouteMatch();
    return (
        <div>
            <Route path={`${match.path}/:topicId`} component={Topic} />
        </div>
    );
}

function Topic() {
    let { topicId } = useParams();
    return <h3>{topicId}</h3>;
}

A) The page renders nothing — useRouteMatch returns null outside a Switch
B) The page renders <h3>hooks</h3>
C) The page renders <h3>/topics/hooks</h3>
D) React throws an error because useParams requires an explicit param name

Q3. What is the difference between history.push('/about') and history.replace('/about') in the useHistory hook?

A) push is asynchronous; replace is synchronous
B) push adds a new history entry (back button works); replace overwrites the current entry (back button skips it)
C) push navigates within a Switch; replace navigates outside it
D) There is no functional difference — both perform the same navigation

Q4. You have a dashboard route that should only be visible to logged-in users. Unauthenticated visitors should land on /login. How would you adapt the PrivateRoute pattern to redirect to /login instead of "/"?

(Open-ended — write the updated Redirect line.)

A1. B — Without exact, React Router treats "/" as a prefix match. Every URL starts with /, so Home would render for /about, /contact, and every other path. The exact prop restricts the match to the full string "/" only.

A2. B — useRouteMatch provides the matched URL/path for the /topics route. The inner Route pattern becomes /topics/:topicId, which matches /topics/hooks and extracts "hooks" as topicId. useParams returns { topicId: "hooks" }, so the component renders <h3>hooks</h3>.

A3. B — history.push appends a new entry to the browser history stack, so the user can press the back button to return. history.replace overwrites the current stack entry, so the previous page is no longer reachable via the back button. Use replace for post-logout redirects or wizard step navigation where going "back" would be confusing.

A4. Change <Redirect to="/" /> to <Redirect to="/login" /> inside PrivateRoute:

isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />

🪞 Recap

  • react-router-dom provides BrowserRouter, Route, Switch, and Link as the four core primitives for declarative SPA routing.
  • The exact prop on the root "/" route prevents it from matching every other path inside a Switch.
  • Nested routes are built by calling useRouteMatch inside a child component to get relative url and path values.
  • useParams extracts dynamic URL segments (:topicId) as a plain object inside any matched route component.
  • useHistory enables programmatic navigation via history.push (adds history entry) or history.replace (overwrites it).
  • The PrivateRoute pattern wraps Route with an auth check and issues a Redirect to guard protected pages.

📚 Further Reading

Like this topic? It’s one of 15 in React Developer.

Block your seat for ₹2,500 and join the next cohort.