You are viewing a preview of this lesson. Sign in to start learning
Back to React

Routing & Navigation

Implement client-side routing for single-page applications

Routing & Navigation in React

Routing and navigation transform React apps from single-page islands into full-featured multi-page experiences, and mastering these concepts with free flashcards and spaced repetition will accelerate your learning journey. This lesson covers React Router fundamentals, dynamic routing patterns, navigation methods, and advanced techniques like nested routes and protected routesβ€”essential skills for building professional React applications.

Welcome to React Routing πŸ—ΊοΈ

Welcome to one of the most practical aspects of React development! If you've ever wondered how applications like Netflix or Airbnb handle different pages while staying lightning-fast, the answer is client-side routing. Unlike traditional multi-page websites that reload entirely when you click a link, React apps use routing libraries to swap components dynamically, creating seamless user experiences.

πŸ’‘ Think of routing like a GPS for your app: just as a GPS updates your view without restarting your car's computer, React Router updates your UI without refreshing the browser.

Core Concepts πŸ’»

What is Client-Side Routing?

Client-side routing means managing navigation entirely in the browser using JavaScript, without requesting new HTML documents from the server. When a user clicks a link:

  1. JavaScript intercepts the click
  2. Updates the browser's URL (using the History API)
  3. React renders the appropriate component
  4. The page appears to change instantlyβ€”no white flash, no loading spinner
Traditional Multi-Page App:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Home    │─GET─→│  Server  │◄─GET─│  About   β”‚
β”‚  .html   β”‚      β”‚          β”‚      β”‚  .html   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     ↓ Full reload      ↑ Full reload      ↓
   πŸ”„ White flash    πŸ”„ White flash    πŸ”„ White flash

React Single-Page App (SPA):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          React App (loaded once)       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Home    β”‚  ─JS─→  β”‚  About   β”‚   β”‚
β”‚  β”‚Component β”‚         β”‚Component β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚        ↓ Instant swap, no reload      β”‚
β”‚              ⚑ No flash               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

React Router Library πŸ“š

React Router is the de facto standard routing library for React. It provides components that enable declarative routingβ€”you describe what should render at each URL using JSX rather than imperatively managing navigation logic.

Key React Router packages:

PackagePurposeUse When
react-router-domWeb applicationsBuilding browser-based apps (most common)
react-router-nativeReact Native appsBuilding mobile applications
react-routerCore libraryUsually imported automatically by the above

Essential Router Components 🧩

BrowserRouter wraps your entire application and provides routing context. Think of it as the power source for all routing features:

import { BrowserRouter } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      {/* Your app components */}
    </BrowserRouter>
  );
}

Routes and Route define which components render for which URLs:

import { Routes, Route } from 'react-router-dom';

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/contact" element={<Contact />} />
</Routes>

⚠️ Important: In React Router v6+, you use <Routes> (plural) wrapping multiple <Route> components. Older versions (v5) used <Switch> instead.

Link creates navigation links that work with React Router (instead of traditional <a> tags):

import { Link } from 'react-router-dom';

<Link to="/about">About Us</Link>

πŸ€” Did you know? Using regular <a> tags would cause full page reloads, destroying all your React state. <Link> prevents the default behavior and updates the URL without refreshing.

Dynamic Routes with URL Parameters πŸ”—

Dynamic routes let you create flexible URL patterns that capture values. Use :paramName syntax:

<Routes>
  <Route path="/users/:userId" element={<UserProfile />} />
  <Route path="/products/:category/:productId" element={<ProductDetail />} />
</Routes>

Inside the component, access parameters using the useParams hook:

import { useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  
  return <h1>Viewing profile for user {userId}</h1>;
}

URL example: /users/42 would render UserProfile with userId = "42".

πŸ’‘ Memory trick - PURE: Params Use Router's Extraction (useParams extracts route parameters)

Sometimes you need to navigate after an action (like form submission). Use the useNavigate hook:

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    // ... authentication logic ...
    navigate('/dashboard'); // Redirect after login
  };
  
  return <form onSubmit={handleSubmit}>...</form>;
}

Navigate options:

navigate('/path');           // Go to path
navigate(-1);                // Go back one page (like browser back)
navigate(1);                 // Go forward one page
navigate('/path', { replace: true }); // Replace current history entry

Nested Routes πŸͺ†

Nested routes create layouts where certain components stay visible while inner content changes:

<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route path="overview" element={<Overview />} />
    <Route path="settings" element={<Settings />} />
    <Route path="profile" element={<Profile />} />
  </Route>
</Routes>

In the parent component, use Outlet to render nested routes:

import { Outlet } from 'react-router-dom';

function DashboardLayout() {
  return (
    <div>
      <nav>{/* Dashboard navigation */}</nav>
      <main>
        <Outlet /> {/* Nested routes render here */}
      </main>
    </div>
  );
}
Nested Route Structure:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     DashboardLayout                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Navigation (always visible)  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚                               β”‚ β”‚
β”‚  β”‚                     β”‚ β”‚
β”‚  β”‚    (nested route renders      β”‚ β”‚
β”‚  β”‚     Overview/Settings/Profile)β”‚ β”‚
β”‚  β”‚                               β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Protected Routes & Conditional Rendering πŸ”’

Protected routes prevent unauthorized access. Create a wrapper component:

import { Navigate } from 'react-router-dom';

function ProtectedRoute({ children, isAuthenticated }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  
  return children;
}

// Usage:
<Route 
  path="/admin" 
  element={
    <ProtectedRoute isAuthenticated={user.isLoggedIn}>
      <AdminPanel />
    </ProtectedRoute>
  } 
/>

Query parameters (?key=value) don't need route definitions. Access them with useSearchParams:

import { useSearchParams } from 'react-router-dom';

function SearchResults() {
  const [searchParams, setSearchParams] = useSearchParams();
  const query = searchParams.get('q');
  const filter = searchParams.get('filter');
  
  return (
    <div>
      <p>Searching for: {query}</p>
      <button onClick={() => setSearchParams({ q: query, filter: 'new' })}>
        Apply Filter
      </button>
    </div>
  );
}

URL example: /search?q=react&filter=popular

The useLocation hook provides information about the current URL:

import { useLocation } from 'react-router-dom';

function Breadcrumbs() {
  const location = useLocation();
  
  return (
    <div>
      <p>Current path: {location.pathname}</p>
      <p>Search: {location.search}</p>
      <p>Hash: {location.hash}</p>
    </div>
  );
}

You can pass state through navigation:

const navigate = useNavigate();
navigate('/profile', { state: { from: 'settings' } });

// In destination component:
const location = useLocation();
console.log(location.state.from); // 'settings'

Examples 🎯

Example 1: Basic Multi-Page Application

Let's build a simple three-page website with home, about, and contact pages:

import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function Home() {
  return (
    <div>
      <h1>Home Page 🏠</h1>
      <p>Welcome to our website!</p>
    </div>
  );
}

function About() {
  return (
    <div>
      <h1>About Us πŸ“–</h1>
      <p>We're a team of passionate developers.</p>
    </div>
  );
}

function Contact() {
  return (
    <div>
      <h1>Contact πŸ“§</h1>
      <p>Email us at hello@example.com</p>
    </div>
  );
}

function Navigation() {
  return (
    <nav style={{ padding: '20px', borderBottom: '2px solid #ccc' }}>
      <Link to="/" style={{ marginRight: '15px' }}>Home</Link>
      <Link to="/about" style={{ marginRight: '15px' }}>About</Link>
      <Link to="/contact">Contact</Link>
    </nav>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Navigation />
      <div style={{ padding: '20px' }}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </div>
    </BrowserRouter>
  );
}

export default App;

What's happening here:

  • BrowserRouter wraps the entire app, enabling routing
  • Navigation component appears on every page (outside Routes)
  • Link components create clickable navigation without page reloads
  • Routes defines which component renders for each URL path
  • Clicking links instantly swaps components without refreshing

Example 2: Dynamic Product Catalog with URL Parameters

Building an e-commerce site where products have unique IDs in the URL:

import React from 'react';
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';

const products = {
  1: { name: 'Laptop', price: 999, description: 'Powerful computing' },
  2: { name: 'Mouse', price: 29, description: 'Ergonomic design' },
  3: { name: 'Keyboard', price: 79, description: 'Mechanical switches' }
};

function ProductList() {
  return (
    <div>
      <h1>Products πŸ›οΈ</h1>
      <ul>
        {Object.keys(products).map(id => (
          <li key={id}>
            <Link to={`/products/${id}`}>{products[id].name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

function ProductDetail() {
  const { productId } = useParams();
  const product = products[productId];
  
  if (!product) {
    return <h2>Product not found 😞</h2>;
  }
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p><strong>Price:</strong> ${product.price}</p>
      <p><strong>Description:</strong> {product.description}</p>
      <Link to="/products">← Back to Products</Link>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/products" element={<ProductList />} />
        <Route path="/products/:productId" element={<ProductDetail />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Key features:

  • Dynamic route /products/:productId captures any ID
  • useParams() extracts the productId from the URL
  • Template literal creates dynamic links: `/products/${id}`
  • Product not found handling for invalid IDs

πŸ”§ Try this: Add a search feature using query parameters like /products?search=laptop

Example 3: Nested Dashboard with Outlet

Creating a dashboard with persistent sidebar navigation:

import React from 'react';
import { BrowserRouter, Routes, Route, Link, Outlet, useNavigate } from 'react-router-dom';

function DashboardLayout() {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', background: '#f0f0f0', padding: '20px' }}>
        <h2>Dashboard</h2>
        <nav>
          <ul style={{ listStyle: 'none', padding: 0 }}>
            <li><Link to="/dashboard/stats">Statistics πŸ“Š</Link></li>
            <li><Link to="/dashboard/users">Users πŸ‘₯</Link></li>
            <li><Link to="/dashboard/settings">Settings βš™οΈ</Link></li>
          </ul>
        </nav>
      </aside>
      <main style={{ flex: 1, padding: '20px' }}>
        <Outlet /> {/* Nested routes render here */}
      </main>
    </div>
  );
}

function Stats() {
  return <h1>Statistics Dashboard πŸ“ˆ</h1>;
}

function Users() {
  return <h1>User Management πŸ‘₯</h1>;
}

function Settings() {
  const navigate = useNavigate();
  
  const handleSave = () => {
    alert('Settings saved!');
    navigate('/dashboard/stats');
  };
  
  return (
    <div>
      <h1>Settings βš™οΈ</h1>
      <button onClick={handleSave}>Save & Return to Stats</button>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route path="stats" element={<Stats />} />
          <Route path="users" element={<Users />} />
          <Route path="settings" element={<Settings />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Architecture breakdown:

  • DashboardLayout renders once, sidebar stays visible
  • <Outlet /> acts as placeholder for nested routes
  • Nested routes don't include parent path (stats not /dashboard/stats)
  • useNavigate enables programmatic navigation after actions
  • The layout persists while inner content swaps

Example 4: Protected Routes with Authentication

Implementing authentication-protected admin routes:

import React, { useState } from 'react';
import { BrowserRouter, Routes, Route, Link, Navigate, useNavigate } from 'react-router-dom';

function ProtectedRoute({ children, isAuthenticated }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  return children;
}

function Login({ onLogin }) {
  const navigate = useNavigate();
  const [username, setUsername] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (username) {
      onLogin(username);
      navigate('/admin');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <h1>Login πŸ”</h1>
      <input 
        type="text" 
        placeholder="Username" 
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );
}

function AdminPanel({ user, onLogout }) {
  return (
    <div>
      <h1>Admin Panel πŸ‘¨β€πŸ’Ό</h1>
      <p>Welcome, {user}!</p>
      <button onClick={onLogout}>Logout</button>
    </div>
  );
}

function Home() {
  return (
    <div>
      <h1>Home 🏠</h1>
      <Link to="/admin">Go to Admin Panel</Link>
    </div>
  );
}

function App() {
  const [user, setUser] = useState(null);
  
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login onLogin={setUser} />} />
        <Route 
          path="/admin" 
          element={
            <ProtectedRoute isAuthenticated={!!user}>
              <AdminPanel user={user} onLogout={() => setUser(null)} />
            </ProtectedRoute>
          } 
        />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Security flow:

  1. User tries accessing /admin
  2. ProtectedRoute checks isAuthenticated
  3. If false, <Navigate to="/login" replace /> redirects
  4. After login, navigate('/admin') takes user to protected page
  5. replace option prevents back-button bypass

⚠️ Security note: This is client-side protection only! Always validate authentication on your backend server.

Common Mistakes ⚠️

❌ Wrong:

<a href="/about">About</a>  // Full page reload!

βœ… Correct:

<Link to="/about">About</Link>  // Client-side navigation

2. Forgetting to Wrap App with BrowserRouter

❌ Wrong:

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

Error: "useRoutes() may be used only in the context of a component"

βœ… Correct:

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

3. Incorrect Nested Route Paths

❌ Wrong:

<Route path="/dashboard" element={<Layout />}>
  <Route path="/dashboard/settings" element={<Settings />} />  // Redundant!
</Route>

βœ… Correct:

<Route path="/dashboard" element={<Layout />}>
  <Route path="settings" element={<Settings />} />  // Relative path
</Route>

4. Missing Outlet in Parent Component

❌ Wrong:

function Layout() {
  return (
    <div>
      <nav>Navigation</nav>
      {/* Forgot <Outlet /> - nested routes won't render! */}
    </div>
  );
}

βœ… Correct:

function Layout() {
  return (
    <div>
      <nav>Navigation</nav>
      <Outlet />  {/* Nested routes render here */}
    </div>
  );
}

5. Using navigate() Outside Router Context

❌ Wrong:

import { useNavigate } from 'react-router-dom';

function externalFunction() {
  const navigate = useNavigate();  // Error! Not in Router context
  navigate('/home');
}

βœ… Correct:

// Only call hooks inside components within <BrowserRouter>
function MyComponent() {
  const navigate = useNavigate();  // βœ“ Works!
  
  const handleClick = () => navigate('/home');
  
  return <button onClick={handleClick}>Go Home</button>;
}

6. Not Handling 404 Routes

❌ Missing:

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  {/* No catch-all route! */}
</Routes>

βœ… Correct:

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="*" element={<NotFound />} />  {/* Catches all unmatched routes */}
</Routes>

Key Takeaways 🎯

  1. React Router enables client-side navigation without page reloads, creating fast, seamless user experiences

  2. BrowserRouter wraps your app and must be at the top level to provide routing context

  3. Use Link instead of tags to prevent full page reloads and maintain React state

  4. Dynamic routes use colon syntax (:paramName) and useParams() extracts values from URLs

  5. Nested routes with Outlet create persistent layouts where parent components stay visible

  6. useNavigate enables programmatic navigation after events like form submissions or API calls

  7. Protected routes use conditional rendering with <Navigate /> to redirect unauthorized users

  8. Query parameters don't need route definitionsβ€”access them with useSearchParams()

  9. Always include a catch-all route (path="*") to handle 404 scenarios gracefully

  10. Router hooks only work inside components wrapped by <BrowserRouter>, not in external functions

πŸ“‹ Quick Reference Card

ComponentPurpose
<BrowserRouter>Wraps app, provides routing context
<Routes>Container for route definitions
<Route>Defines path-to-component mapping
<Link>Navigation link (replaces <a>)
<Outlet>Renders nested routes in parent
<Navigate>Redirects to different route
useParams()Extracts URL parameters
useNavigate()Programmatic navigation function
useLocation()Current URL location info
useSearchParams()Read/write query parameters

Common Patterns:

path="/"Home route
path="/about"Static route
path="/users/:id"Dynamic parameter
path="*"404 catch-all
navigate(-1)Go back
navigate('/path', {replace: true})Replace history

πŸ“š Further Study

  1. React Router Official Documentation: https://reactrouter.com/ - Comprehensive guides and API reference with examples

  2. MDN History API: https://developer.mozilla.org/en-US/docs/Web/API/History_API - Understanding the browser API powering React Router

  3. React Router Tutorial by Web Dev Simplified: https://www.youtube.com/watch?v=Ul3y1LXxzdU - Video walkthrough of routing fundamentals and advanced patterns