React.js Fundamentals

React Router: Client-Side Routing

20 min Lesson 16 of 40

Introduction to React Router

React Router is the standard routing library for React applications. It enables navigation between different views of your application, manages browser history, and keeps the UI in sync with the URL—all without page reloads.

Note: This lesson covers React Router v6, which introduced significant changes from v5. Make sure you're installing the correct version: npm install react-router-dom@6

Setting Up React Router

First, install React Router in your project:

# Install React Router
npm install react-router-dom

# Or with Yarn
yarn add react-router-dom

The core setup involves wrapping your application with BrowserRouter and defining routes:

// src/main.jsx or src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

Defining Routes with Routes and Route

In React Router v6, use the Routes component to wrap all your Route components:

// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';

function App() {
  return (
    <div className="app">
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

export default App;

Tip: The path="*" route acts as a catch-all for unmatched URLs, perfect for 404 pages. It should always be the last route.

Navigation with Link and NavLink

React Router provides Link and NavLink components for navigation without full page reloads:

// src/components/Navigation.jsx
import { Link, NavLink } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      {/* Basic Link - no styling based on active state */}
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>

      {/* NavLink - receives "active" class when route matches */}
      <NavLink
        to="/"
        className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
      >
        Home
      </NavLink>

      <NavLink
        to="/about"
        style={({ isActive }) => ({
          color: isActive ? '#ff6b6b' : '#333',
          fontWeight: isActive ? 'bold' : 'normal'
        })}
      >
        About
      </NavLink>
    </nav>
  );
}

export default Navigation;

Key Difference: Link is for basic navigation, while NavLink provides active state styling, making it ideal for navigation menus.

Nested Routes

React Router v6 makes nested routing more intuitive with the Outlet component:

// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Profile from './pages/dashboard/Profile';
import Settings from './pages/dashboard/Settings';
import Analytics from './pages/dashboard/Analytics';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />

        {/* Nested routes under /dashboard */}
        <Route path="dashboard" element={<Dashboard />}>
          <Route index element={<Profile />} />
          <Route path="profile" element={<Profile />} />
          <Route path="settings" element={<Settings />} />
          <Route path="analytics" element={<Analytics />} />
        </Route>
      </Route>
    </Routes>
  );
}

// src/components/Layout.jsx
import { Outlet } from 'react-router-dom';
import Navigation from './Navigation';
import Footer from './Footer';

function Layout() {
  return (
    <div>
      <Navigation />
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
      <Footer />
    </div>
  );
}

// src/pages/Dashboard.jsx
import { Outlet, Link } from 'react-router-dom';

function Dashboard() {
  return (
    <div className="dashboard">
      <aside>
        <Link to="/dashboard/profile">Profile</Link>
        <Link to="/dashboard/settings">Settings</Link>
        <Link to="/dashboard/analytics">Analytics</Link>
      </aside>
      <section>
        <Outlet /> {/* Nested dashboard routes render here */}
      </section>
    </div>
  );
}

URL Parameters

Dynamic routes use URL parameters to pass data through the URL:

// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import UserProfile from './pages/UserProfile';
import BlogPost from './pages/BlogPost';

function App() {
  return (
    <Routes>
      {/* Single parameter */}
      <Route path="/users/:userId" element={<UserProfile />} />

      {/* Multiple parameters */}
      <Route path="/blog/:category/:postId" element={<BlogPost />} />

      {/* Optional parameters with ? */}
      <Route path="/products/:productId/:variant?" element={<Product />} />
    </Routes>
  );
}

// src/pages/UserProfile.jsx
import { useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';

function UserProfile() {
  const { userId } = useParams();
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Fetch user data based on userId
    fetch(`https://api.example.com/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]);

  if (loading) return <div>Loading user...</div>
  if (!user) return <div>User not found</div>

  return (
    <div>
      <h1>{user.name}'s Profile</h1>
      <p>User ID: {userId}</p>
      <p>Email: {user.email}</p>
    </div>
  );
}

// src/pages/BlogPost.jsx
import { useParams } from 'react-router-dom';

function BlogPost() {
  const { category, postId } = useParams();

  return (
    <div>
      <h1>Blog Post</h1>
      <p>Category: {category}</p>
      <p>Post ID: {postId}</p>
    </div>
  );
}

Warning: URL parameters are always strings. If you need numbers, convert them: const id = parseInt(params.userId, 10);

Index Routes

Index routes render when the parent route matches exactly:

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        {/* This renders at exactly "/" */}
        <Route index element={<Home />} />

        {/* This renders at "/about" */}
        <Route path="about" element={<About />} />

        <Route path="products" element={<ProductsLayout />}>
          {/* This renders at exactly "/products" */}
          <Route index element={<ProductsList />} />

          {/* This renders at "/products/:id" */}
          <Route path=":id" element={<ProductDetail />} />
        </Route>
      </Route>
    </Routes>
  );
}

Exercise 1: Basic Routing Setup

Task: Create a multi-page application with the following routes:

  • Home page at /
  • About page at /about
  • Services page at /services
  • Contact page at /contact
  • 404 page for unmatched routes

Add a navigation bar with NavLink components that highlight the active page.

Exercise 2: Nested Routes with Products

Task: Create a products section with nested routes:

  • /products - Shows a list of product categories
  • /products/:categoryId - Shows products in that category
  • /products/:categoryId/:productId - Shows product details

Use sample data and the useParams hook to display the correct information.

Exercise 3: User Dashboard

Task: Build a user dashboard with nested routes:

  • /dashboard - Dashboard layout with sidebar
  • /dashboard/overview - Overview page (index route)
  • /dashboard/orders - Orders list
  • /dashboard/profile - User profile

Use Outlet to render nested content and create a sidebar with NavLink for navigation.

Summary

In this lesson, you learned the fundamentals of React Router v6:

  • Setting up BrowserRouter and defining routes with Routes and Route
  • Navigation with Link and NavLink components
  • Creating nested routes with Outlet
  • Using URL parameters with useParams
  • Implementing index routes for default content
  • Building catch-all routes for 404 pages

In the next lesson, we'll explore advanced routing techniques including programmatic navigation, protected routes, and lazy loading.