Topic 8 of 56 · Full Stack Advanced

Topic 8 : React Router

Lesson TL;DRTopic 8: React Router 📖 11 min read · 🎯 intermediate · 🧭 Prerequisites: buildingahttpserverwithnodejsusinghttpapis, http Why this matters Up until now, every React app you've built probably lives o...
11 min read·intermediate·react · react-router · routing · spa

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-dom in a Create React App project
  • Define declarative routes with BrowserRouter, Switch, Route, and Link
  • Build nested routes using useRouteMatch and useParams
  • Navigate programmatically with the useHistory hook
  • Protect routes with a PrivateRoute component 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:

PrimitiveRole
BrowserRouterWraps the app; manages history via the HTML5 History API
SwitchRenders the first Route that matches the current URL
RouteDeclares a path-to-component mapping
LinkRenders an <a> tag that updates the URL without reloading
RedirectImmediately sends the user to a different path
useHistoryHook that returns the history object for imperative navigation
useParamsHook that returns URL parameters from the current route
useRouteMatchHook 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 exact prop on the / route is critical — without it, every URL matches / because every path starts with a slash, and Switch would 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.

  1. Create src/components/Profile.js that reads a :userId param with useParams and displays "Profile for user: {userId}".
  2. Add a <Route path="/profile/:userId" component={Profile} /> inside the Switch in App.js.
  3. 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-dom provides BrowserRouter, Switch, Route, and Link as the four core building blocks of client-side routing in React.
  • The exact prop on a Route prevents prefix matching, which is essential for the root / path.
  • Nested routes are composed using useRouteMatch for relative path construction and useParams to read URL parameters.
  • useHistory exposes history.push and history.replace for programmatic navigation outside of click handlers on Link components.
  • PrivateRoute wraps React Router's Route with auth logic and uses Redirect to enforce access control declaratively.

📚 Further Reading

Like this topic? It’s one of 56 in Full Stack Advanced.

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