Client-side Routing with React Router

Building Single Page Applications with Navigation

Welcome to React Router!

Imagine walking through a museum where instead of moving between rooms, the room transforms around you as you choose different exhibits. That's client-side routing! The URL changes, but the page doesn't reload - content simply appears and disappears, creating a smooth, app-like experience.

graph LR A[Browser URL] --> B[React Router] B --> C{Route Matcher} C --> D[Home Component] C --> E[About Component] C --> F[Products Component] C --> G[404 Component] style A fill:#ff9999 style B fill:#99ff99 style C fill:#9999ff

Traditional vs Client-side Routing

Traditional Server-side Routing

  1. User clicks a link
  2. Browser sends request to server
  3. Server processes request
  4. Server sends new HTML page
  5. Browser reloads entire page
graph TD A[Click Link] --> B[Browser Request] B --> C[Server] C --> D[New HTML] D --> E[Full Page Reload] style E fill:#ff9999

Client-side Routing

  1. User clicks a link
  2. React Router intercepts click
  3. URL changes without reload
  4. Router renders new component
  5. Content updates instantly
graph TD A[Click Link] --> B[React Router] B --> C[URL Update] C --> D[Component Render] D --> E[Content Update] style E fill:#99ff99

Setting Up React Router

// Installation
npm install react-router-dom

// Basic Setup
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Components
function Home() {
  return <h1>Welcome Home!</h1>;
}

function About() {
  return <h1>About Us</h1>;
}

function Contact() {
  return <h1>Contact Us</h1>;
}

function NotFound() {
  return <h1>404 - Page Not Found</h1>;
}

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

Core Router Components

1. BrowserRouter

The foundation that enables routing in your app. It uses HTML5 history API to keep UI in sync with URL.

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

function App() {
  return (
    <BrowserRouter>
      {/* All your app components go here */}
    </BrowserRouter>
  );
}

2. Routes and Route

Define your application's route structure.

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

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/users" element={<Users />} />
      <Route path="/users/:id" element={<UserProfile />} />
      <Route path="/dashboard/*" element={<Dashboard />} />
    </Routes>
  );
}

3. Link Component

Navigate without page reloads - the magic of client-side routing!

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

function Navigation() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      <Link to="/contact">Contact</Link>
    </nav>
  );
}

// Comparison with regular anchor tags
function BadNavigation() {
  return (
    <nav>
      {/* ❌ These cause full page reloads! */}
      <a href="/">Home</a>
      <a href="/about">About</a>
    </nav>
  );
}

4. NavLink Component

Like Link, but with styling capabilities for active routes.

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

function Navigation() {
  return (
    <nav>
      <NavLink 
        to="/" 
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        Home
      </NavLink>
      <NavLink 
        to="/about"
        style={({ isActive }) => ({
          color: isActive ? 'red' : 'black',
          fontWeight: isActive ? 'bold' : 'normal'
        })}
      >
        About
      </NavLink>
    </nav>
  );
}

Dynamic Routes and Parameters

URL Parameters

// Route definition
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />

// Accessing parameters
import { useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  
  return <h1>User Profile: {userId}</h1>;
}

function Comment() {
  const { postId, commentId } = useParams();
  
  return (
    <div>
      <h2>Post: {postId}</h2>
      <h3>Comment: {commentId}</h3>
    </div>
  );
}

Query Parameters

// Reading query parameters
import { useSearchParams } from 'react-router-dom';

function SearchResults() {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const query = searchParams.get('q');
  const page = searchParams.get('page') || 1;
  const sort = searchParams.get('sort') || 'relevance';
  
  // Update query parameters
  const updatePage = (newPage) => {
    setSearchParams(prev => {
      prev.set('page', newPage);
      return prev;
    });
  };
  
  return (
    <div>
      <h1>Search Results for: {query}</h1>
      <p>Page: {page}, Sort: {sort}</p>
      <button onClick={() => updatePage(parseInt(page) + 1)}>
        Next Page
      </button>
    </div>
  );
}

// Creating links with query parameters
function SearchForm() {
  const [query, setQuery] = useState('');
  
  return (
    <form>
      <input 
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <Link to={`/search?q=${encodeURIComponent(query)}`}>
        Search
      </Link>
    </form>
  );
}

Protected Routes

// Protected Route Component
function ProtectedRoute({ children }) {
  const { user, loading } = useAuth();
  const location = useLocation();
  
  if (loading) {
    return <LoadingSpinner />;
  }
  
  if (!user) {
    // Redirect to login, but save the attempted URL
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return children;
}

// Usage
function App() {
  return (
    <Routes>
      <Route path="/login" element={<Login />} />
      <Route 
        path="/dashboard" 
        element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        } 
      />
      <Route 
        path="/profile" 
        element={
          <ProtectedRoute>
            <Profile />
          </ProtectedRoute>
        } 
      />
    </Routes>
  );
}

// Role-based Route Protection
function RoleBasedRoute({ children, allowedRoles }) {
  const { user } = useAuth();
  
  if (!user || !allowedRoles.includes(user.role)) {
    return <Navigate to="/unauthorized" replace />;
  }
  
  return children;
}

// Usage
<Route 
  path="/admin" 
  element={
    <RoleBasedRoute allowedRoles={['admin']}>
      <AdminPanel />
    </RoleBasedRoute>
  } 
/>

Layouts and Nested Routes

// Layout Component
function MainLayout() {
  return (
    <div className="layout">
      <Header />
      <nav>
        <Link to="/">Home</Link>
        <Link to="/products">Products</Link>
        <Link to="/about">About</Link>
      </nav>
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
      <Footer />
    </div>
  );
}

// App with Layout
function App() {
  return (
    <Routes>
      <Route element={<MainLayout />}>
        <Route path="/" element={<Home />} />
        <Route path="products" element={<Products />} />
        <Route path="about" element={<About />} />
      </Route>
      
      {/* Different layout for auth pages */}
      <Route element={<AuthLayout />}>
        <Route path="login" element={<Login />} />
        <Route path="register" element={<Register />} />
      </Route>
      
      {/* No layout */}
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}

Error Handling in Routes

// Error Boundary for Routes
import { useRouteError } from 'react-router-dom';

function ErrorBoundary() {
  const error = useRouteError();
  
  return (
    <div className="error-page">
      <h1>Oops! Something went wrong</h1>
      <p>{error.statusText || error.message}</p>
      <Link to="/">Go back home</Link>
    </div>
  );
}

// Using Error Boundary
function App() {
  return (
    <Routes>
      <Route 
        path="/" 
        element={<Home />} 
        errorElement={<ErrorBoundary />} 
      />
      <Route 
        path="/users/:id" 
        element={<UserProfile />} 
        errorElement={<ErrorBoundary />}
      />
    </Routes>
  );
}

// Handling 404s
function NotFound() {
  return (
    <div className="not-found">
      <h1>404 - Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
      <Link to="/">Go to Homepage</Link>
    </div>
  );
}

// Catch-all route for 404s
<Route path="*" element={<NotFound />} />

React Router Best Practices

1. Use Descriptive Route Paths

// ✅ Good - descriptive and hierarchical
<Route path="/users/:userId/settings" element={<UserSettings />} />
<Route path="/products/:category/:productId" element={<ProductDetail />} />

// ❌ Bad - unclear and flat
<Route path="/s/:id" element={<UserSettings />} />
<Route path="/p/:id" element={<ProductDetail />} />

2. Handle Loading States

function UserProfile() {
  const { userId } = useParams();
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUser(userId)
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);
  
  if (loading) {
    return <LoadingSpinner />;
  }
  
  if (!user) {
    return <Navigate to="/404" replace />;
  }
  
  return <UserProfileContent user={user} />;
}

3. Use Route Groups for Organization

// Group related routes
function App() {
  return (
    <Routes>
      {/* Public routes */}
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      
      {/* Auth routes */}
      <Route path="/auth">
        <Route path="login" element={<Login />} />
        <Route path="register" element={<Register />} />
        <Route path="forgot-password" element={<ForgotPassword />} />
      </Route>
      
      {/* Protected routes */}
      <Route element={<ProtectedRoute />}>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Route>
    </Routes>
  );
}

Practice Exercises

Exercise 1: Build a Blog Navigation

Create a blog with the following routes:

  • Home page with recent posts
  • Individual post pages (/posts/:postId)
  • Category pages (/categories/:categoryName)
  • Author pages (/authors/:authorId)
  • Search results page with query parameters

Exercise 2: Create a Multi-step Form

Build a multi-step form using routes:

  • /signup/personal-info
  • /signup/account-details
  • /signup/preferences
  • /signup/confirmation

Include navigation between steps and prevent access to later steps until earlier ones are complete.

Exercise 3: Implement Route Guards

Create different types of route protection:

  • Authentication guard
  • Role-based access control
  • Subscription-based access

Real-World Example: E-commerce Routes

// Complete routing structure for an e-commerce app
function EcommerceApp() {
  return (
    <BrowserRouter>
      <Routes>
        {/* Public routes */}
        <Route element={<MainLayout />}>
          <Route path="/" element={<HomePage />} />
          <Route path="/products" element={<ProductList />} />
          <Route path="/products/:productId" element={<ProductDetail />} />
          <Route path="/categories/:categorySlug" element={<CategoryPage />} />
          <Route path="/search" element={<SearchResults />} />
          <Route path="/cart" element={<Cart />} />
        </Route>
        
        {/* Auth routes */}
        <Route element={<AuthLayout />}>
          <Route path="/login" element={<Login />} />
          <Route path="/register" element={<Register />} />
          <Route path="/forgot-password" element={<ForgotPassword />} />
        </Route>
        
        {/* Protected routes */}
        <Route element={<ProtectedRoute />}>
          <Route element={<MainLayout />}>
            <Route path="/account" element={<Account />}>
              <Route index element={<AccountOverview />} />
              <Route path="orders" element={<OrderHistory />} />
              <Route path="orders/:orderId" element={<OrderDetail />} />
              <Route path="addresses" element={<AddressBook />} />
              <Route path="payment-methods" element={<PaymentMethods />} />
            </Route>
            <Route path="/checkout" element={<Checkout />}>
              <Route path="shipping" element={<ShippingStep />} />
              <Route path="payment" element={<PaymentStep />} />
              <Route path="review" element={<ReviewStep />} />
              <Route path="confirmation" element={<Confirmation />} />
            </Route>
          </Route>
        </Route>
        
        {/* Admin routes */}
        <Route element={<RoleBasedRoute allowedRoles={['admin']} />}>
          <Route path="/admin" element={<AdminLayout />}>
            <Route index element={<AdminDashboard />} />
            <Route path="products" element={<ProductManagement />} />
            <Route path="orders" element={<OrderManagement />} />
            <Route path="users" element={<UserManagement />} />
          </Route>
        </Route>
        
        {/* Error routes */}
        <Route path="/404" element={<NotFound />} />
        <Route path="/500" element={<ServerError />} />
        <Route path="*" element={<Navigate to="/404" replace />} />
      </Routes>
    </BrowserRouter>
  );
}

Key Takeaways