Introduction to Basic Routing & Data Flow in SPAs

Welcome to Chapter 3! In the previous chapters, we laid the groundwork by understanding React’s component-based architecture and how to manage local component state. Now, it’s time to make our applications feel truly dynamic and responsive. Imagine clicking a link on a website and seeing the content change instantly, without the entire page reloading. This magic is largely thanks to client-side routing, a cornerstone of Single Page Applications (SPAs).

In this chapter, we’ll dive deep into how SPAs navigate between different views using React Router, the most popular routing library for React. We’ll learn how to set up routes, link between pages, and access URL parameters. Alongside routing, we’ll solidify our understanding of how data moves through our React applications, exploring essential patterns like props, component state, and the Context API. Mastering these concepts is crucial for building scalable and maintainable React applications, as they dictate how users interact with your app and how information is managed behind the scenes.

By the end of this chapter, you’ll be able to build a multi-page SPA where users can navigate seamlessly, and components can effectively share and manage their data. Get ready to transform your static React components into an interconnected, interactive application!

Core Concepts: Navigating and Communicating in Your SPA

Before we jump into code, let’s build a strong mental model for how routing and data flow work in a modern React SPA.

What is Client-Side Routing?

In traditional web applications, clicking a link or submitting a form typically sends a request to the server, which then responds with a brand new HTML page. This causes a full page reload, often perceived as a flicker or delay.

Client-Side Routing, a hallmark of Single Page Applications (SPAs), completely changes this. Instead of requesting a new HTML page from the server for every navigation, the browser’s JavaScript takes over. When you click a link:

  1. The JavaScript intercepts the navigation event.
  2. It prevents the default browser behavior (full page reload).
  3. It dynamically updates the URL in the browser’s address bar using the History API (a browser feature that allows JavaScript to manipulate the browser’s session history).
  4. It then renders the appropriate React components corresponding to the new URL, all without leaving the current HTML page.

This approach offers a much smoother, faster, and more app-like user experience.

Think of it like this: Instead of going to a new library every time you want a different book, you stay in the same reading room, and a librarian (your React app) quickly brings you the next book (component) you requested.

flowchart TD A[User clicks Link] --> B{Browser intercepts event}; B -->|Prevent default| C[React Router handles navigation]; C --> D[Update URL in browser]; D --> E[Render new React Components]; E --> F[Display updated UI];

Introducing React Router v6

While the browser’s History API provides the low-level mechanism, using it directly can be complex. That’s where routing libraries come in. react-router-dom is the de-facto standard for routing in React applications. As of February 14, 2026, react-router-dom v6.x is the stable and recommended version, offering a modern, hooks-based API.

Here are the core components and hooks you’ll encounter:

  • BrowserRouter: This is the top-level component that wraps your entire application. It uses the HTML5 History API to keep your UI in sync with the URL. You typically place it in your src/main.jsx (or index.js) file.
  • Routes: A wrapper component that groups individual <Route> elements. It’s responsible for finding the best match among its children <Route>s.
  • Route: Defines a mapping between a URL path and a React component. When the URL matches the path prop, the component specified by the element prop is rendered.
  • Link: The primary way to navigate around your application. It renders an accessible <a> tag but prevents the default full page reload, allowing React Router to handle the navigation.
  • useNavigate: A hook that allows you to programmatically navigate (e.g., after a form submission, or clicking a “Go Back” button). It returns a function you can call with a path.
  • useParams: A hook that lets you access URL parameters (e.g., /products/123 where 123 is the productId).

Basic Data Flow in React SPAs

How do different parts of your application communicate? React promotes a unidirectional data flow, meaning data primarily flows down the component tree from parent to child.

  1. Props (Properties):

    • What: The primary mechanism for passing data from a parent component to its child components. Props are immutable within the child component.
    • Why: To configure child components, pass down data to be displayed, or provide functions for children to call back to the parent.
    • How: Parent passes data as attributes on the child component (e.g., <ChildComponent message="Hello" />). Child receives them as an argument to its function component (e.g., function ChildComponent({ message }) { ... }).
    • Mental Model: Like handing a book to someone below you on a ladder. They can read it, but they can’t change the original copy.
  2. State (Local Component State):

    • What: Data that a component manages internally and can change over time. It makes a component dynamic and interactive.
    • Why: To track user input, toggle UI elements, manage loading states, or store data fetched from an API that’s specific to that component’s lifecycle.
    • How: Using the useState hook (e.g., const [count, setCount] = useState(0);).
    • Mental Model: A sticky note you keep on your own desk; you can write on it and change it, but others only see the current version if you show it to them.
  3. Context API:

    • What: A way to share data (like user authentication status, theme settings, or fetched global data) across a component tree without manually passing props down through every level (known as “prop drilling”).
    • Why: To avoid prop drilling when many components need access to the same global-like data.
    • How: You create a Context object, provide a Context.Provider higher up in your tree with the value, and consume it with the useContext hook in any descendant component.
    • Mental Model: A shared whiteboard in a meeting room. Anyone in the room can look at it or write on it (if allowed), without needing someone to physically hand them the information.
  4. Data Fetching (Introduction):

    • What: Retrieving data from external sources, typically APIs.
    • Why: To display dynamic content, user-specific information, or interact with backend services.
    • How: Using the browser’s built-in fetch API or a library like axios within a useEffect hook. The useEffect hook is crucial for performing side effects like data fetching in function components, ensuring it runs at appropriate times (e.g., on component mount).
    • Mental Model: Ordering food from a restaurant. You send a request, wait for the response, and then display what you received.

Step-by-Step Implementation: Building a Simple Product Viewer

Let’s put these concepts into practice by building a basic product viewer application. This app will have a home page, a list of products, and individual product detail pages, all navigated client-side.

Project Setup

First, let’s set up our React project. We’ll use Vite, a modern build tool that offers a much faster development experience than create-react-app.

  1. Create a new Vite React project: Open your terminal and run:

    npm create vite@latest my-product-viewer -- --template react
    
    • npm create vite@latest: This command uses npm to create a new project with the latest Vite.
    • my-product-viewer: This will be the name of our project directory.
    • --template react: This specifies that we want a React template.
  2. Navigate into your project folder:

    cd my-product-viewer
    
  3. Install dependencies:

    npm install
    
  4. Install react-router-dom: We need the routing library! As of 2026-02-14, react-router-dom v6.22.1 is a recent stable version.

    npm install react-router-dom@6.22.1
    
  5. Start the development server:

    npm run dev
    

    Your browser should open to http://localhost:5173 (or a similar port). You’ll see the default Vite + React starter page.

Step 1: Initial App Structure & Navbar

Let’s clean up our project and add a basic navigation bar.

  1. Open src/App.jsx: Replace its content with a clean functional component.

    // src/App.jsx
    import React from 'react';
    import { Outlet } from 'react-router-dom'; // We'll use Outlet later for nested routes
    import Navbar from './components/Navbar';
    
    function App() {
      return (
        <div>
          <Navbar />
          <div className="container">
            {/* This is where our routed components will render */}
            <Outlet />
          </div>
        </div>
      );
    }
    
    export default App;
    
    • Explanation: We import Navbar (which we’ll create next) and Outlet from react-router-dom. Outlet is a placeholder that will render the child routes of App.
  2. Create src/components directory and Navbar.jsx: Inside src, create a new folder named components. Inside components, create Navbar.jsx.

    // src/components/Navbar.jsx
    import React from 'react';
    import { Link } from 'react-router-dom';
    import './Navbar.css'; // We'll create this CSS file next
    
    function Navbar() {
      return (
        <nav className="navbar">
          <Link to="/" className="navbar-brand">
            My Product Viewer
          </Link>
          <ul className="navbar-nav">
            <li className="nav-item">
              <Link to="/" className="nav-link">
                Home
              </Link>
            </li>
            <li className="nav-item">
              <Link to="/products" className="nav-link">
                Products
              </Link>
            </li>
          </ul>
        </nav>
      );
    }
    
    export default Navbar;
    
    • Explanation: We import Link from react-router-dom. Instead of a regular <a> tag, Link prevents full page reloads and allows React Router to handle the navigation. The to prop specifies the target path.
  3. Create src/components/Navbar.css: Let’s add some minimal styling for our navbar.

    /* src/components/Navbar.css */
    .navbar {
      background-color: #333;
      padding: 1rem;
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: white;
    }
    
    .navbar-brand {
      color: white;
      text-decoration: none;
      font-size: 1.5rem;
      font-weight: bold;
    }
    
    .navbar-nav {
      list-style: none;
      margin: 0;
      padding: 0;
      display: flex;
    }
    
    .nav-item {
      margin-left: 1rem;
    }
    
    .nav-link {
      color: white;
      text-decoration: none;
      padding: 0.5rem 1rem;
      border-radius: 4px;
      transition: background-color 0.3s ease;
    }
    
    .nav-link:hover {
      background-color: #555;
    }
    
    .container {
      padding: 20px;
      max-width: 960px;
      margin: 20px auto;
      background-color: #f9f9f9;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }
    
  4. Clean up src/index.css (optional but good practice): Remove default Vite/React styles to start fresh. You can leave a basic body style.

    /* src/index.css */
    body {
      margin: 0;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
        'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
        sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      background-color: #eee;
    }
    
    code {
      font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
        monospace;
    }
    

Step 2: Define Routes

Now, let’s configure react-router-dom in our main entry file.

  1. Open src/main.jsx: This is where we’ll set up BrowserRouter and our application routes using the createBrowserRouter function, a modern approach for React Router v6.

    // src/main.jsx
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { createBrowserRouter, RouterProvider } from 'react-router-dom';
    import App from './App.jsx';
    import Home from './pages/Home.jsx'; // We'll create these pages next
    import Products from './pages/Products.jsx';
    import ProductDetail from './pages/ProductDetail.jsx';
    import './index.css';
    
    // 1. Define our routes configuration
    const router = createBrowserRouter([
      {
        path: '/', // The root path
        element: <App />, // The App component will be rendered here
        children: [ // Nested routes will render inside App's <Outlet />
          {
            index: true, // This marks the Home component as the default child route for '/'
            element: <Home />,
          },
          {
            path: 'products', // Path for the product list (relative to '/')
            element: <Products />,
          },
          {
            path: 'products/:productId', // Path for individual product details, with a dynamic parameter
            element: <ProductDetail />,
          },
        ],
      },
    ]);
    
    ReactDOM.createRoot(document.getElementById('root')).render(
      <React.StrictMode>
        {/* 2. Provide the router to our application */}
        <RouterProvider router={router} />
      </React.StrictMode>,
    );
    
    • Explanation:
      • We import createBrowserRouter and RouterProvider from react-router-dom. This is the recommended way to set up routing in v6 for better performance and server-side rendering compatibility.
      • createBrowserRouter takes an array of route objects.
      • The root route (/) uses our App component as its element. This means App will always render, and its Outlet will display the matching children routes.
      • index: true on the Home route makes it the default content for the / path when no other child path matches.
      • path: 'products/:productId' defines a dynamic segment (:productId). This allows us to capture values from the URL, like products/123, where 123 would be productId.
      • Finally, RouterProvider wraps our entire application and makes the router available.
  2. Create src/pages directory and placeholder pages: Inside src, create a new folder named pages. Inside pages, create Home.jsx, Products.jsx, and ProductDetail.jsx.

    // src/pages/Home.jsx
    import React from 'react';
    
    function Home() {
      return (
        <div>
          <h1>Welcome to the Product Viewer!</h1>
          <p>Explore our amazing range of products.</p>
        </div>
      );
    }
    
    export default Home;
    
    // src/pages/Products.jsx
    import React from 'react';
    
    function Products() {
      return (
        <div>
          <h1>Our Products</h1>
          <p>Loading products...</p>
        </div>
      );
    }
    
    export default Products;
    
    // src/pages/ProductDetail.jsx
    import React from 'react';
    
    function ProductDetail() {
      return (
        <div>
          <h1>Product Detail</h1>
          <p>Loading product details...</p>
        </div>
      );
    }
    
    export default ProductDetail;
    

    Now, if you refresh your browser, you should see the Navbar and the “Welcome to the Product Viewer!” message. Clicking “Products” will take you to /products and show “Our Products”. The routing is working!

Step 3: Displaying Product List

Let’s make our Products page display a list of mock products.

  1. Update src/pages/Products.jsx: We’ll create some dummy product data and render it. Each product will have a link to its detail page.

    // src/pages/Products.jsx
    import React from 'react';
    import { Link } from 'react-router-dom';
    
    // Mock product data
    const products = [
      { id: '1', name: 'Laptop Pro', price: 1200, description: 'Powerful laptop for professionals.' },
      { id: '2', name: 'Wireless Mouse', price: 25, description: 'Ergonomic mouse with long battery life.' },
      { id: '3', name: 'Mechanical Keyboard', price: 90, description: 'Clicky keys for the best typing experience.' },
      { id: '4', name: '4K Monitor', price: 350, description: 'Stunning visuals for work and play.' },
    ];
    
    function Products() {
      return (
        <div>
          <h1>Our Products</h1>
          <div className="product-list">
            {products.map((product) => (
              <div key={product.id} className="product-card">
                <h2>{product.name}</h2>
                <p>${product.price}</p>
                {/* Link to the individual product detail page */}
                <Link to={`/products/${product.id}`} className="view-details-link">
                  View Details
                </Link>
              </div>
            ))}
          </div>
        </div>
      );
    }
    
    export default Products;
    
    • Explanation: We map over our products array. For each product, we render its name and price. Crucially, we use Link to={/products/${product.id}} to create a dynamic link. When clicked, this will navigate to a URL like /products/1 or /products/2.
  2. Add some basic styling for products (e.g., in src/index.css or a new Products.css):

    /* Add to src/index.css or a new src/pages/Products.css */
    .product-list {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 20px;
      margin-top: 20px;
    }
    
    .product-card {
      background-color: white;
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 20px;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
      display: flex;
      flex-direction: column;
      justify-content: space-between;
    }
    
    .product-card h2 {
      margin-top: 0;
      color: #333;
    }
    
    .product-card p {
      color: #666;
      font-size: 0.95rem;
    }
    
    .view-details-link {
      display: inline-block;
      margin-top: 15px;
      padding: 8px 15px;
      background-color: #007bff;
      color: white;
      text-decoration: none;
      border-radius: 5px;
      align-self: flex-start;
      transition: background-color 0.2s ease;
    }
    
    .view-details-link:hover {
      background-color: #0056b3;
    }
    

    Refresh your browser, go to the Products page, and you should see the list. Click on any product’s “View Details” link. The URL will change, but the content will still say “Loading product details…” This is because our ProductDetail component isn’t yet using the productId from the URL.

Step 4: Product Detail Page & useParams

Now, let’s make the ProductDetail component dynamic.

  1. Update src/pages/ProductDetail.jsx: We’ll use the useParams hook to get the productId from the URL and then display the corresponding product’s information. We’ll also add a “Go Back” button using useNavigate.

    // src/pages/ProductDetail.jsx
    import React from 'react';
    import { useParams, useNavigate } from 'react-router-dom';
    
    // Re-use the mock product data
    const products = [
      { id: '1', name: 'Laptop Pro', price: 1200, description: 'Powerful laptop for professionals.', details: '16GB RAM, 512GB SSD, Intel i7' },
      { id: '2', name: 'Wireless Mouse', price: 25, description: 'Ergonomic mouse with long battery life.', details: 'Bluetooth 5.0, 1000 DPI, 2-year warranty' },
      { id: '3', name: 'Mechanical Keyboard', price: 90, description: 'Clicky keys for the best typing experience.', details: 'Cherry MX Blue switches, RGB backlight, full-size' },
      { id: '4', name: '4K Monitor', price: 350, description: 'Stunning visuals for work and play.', details: '27-inch IPS, 3840x2160 resolution, USB-C hub' },
    ];
    
    function ProductDetail() {
      const { productId } = useParams(); // Get the productId from the URL
      const navigate = useNavigate(); // Get the navigate function
    
      // Find the product based on the ID
      const product = products.find((p) => p.id === productId);
    
      if (!product) {
        return (
          <div>
            <h1>Product Not Found</h1>
            <p>The product with ID "{productId}" does not exist.</p>
            <button onClick={() => navigate('/products')}>Go to Products</button>
          </div>
        );
      }
    
      return (
        <div className="product-detail-card">
          <h1>{product.name}</h1>
          <p className="product-price">${product.price}</p>
          <p>{product.description}</p>
          <p><strong>Details:</strong> {product.details}</p>
          <button onClick={() => navigate(-1)} className="go-back-button">
            Go Back
          </button>
        </div>
      );
    }
    
    export default ProductDetail;
    
    • Explanation:
      • useParams() returns an object where keys are the parameter names defined in the Route (:productId) and values are the actual values from the URL. We destructure productId from it.
      • useNavigate() returns a function that allows us to navigate programmatically. navigate(-1) is a special value that navigates one step back in the browser’s history, similar to clicking the browser’s back button.
      • We then use productId to find the corresponding product in our mock data.
      • If no product is found, we display a “Product Not Found” message and offer a button to go back to the product list.
  2. Add some basic styling for product detail (e.g., in src/index.css or a new ProductDetail.css):

    /* Add to src/index.css or a new src/pages/ProductDetail.css */
    .product-detail-card {
      background-color: white;
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 30px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      margin-top: 20px;
    }
    
    .product-detail-card h1 {
      color: #333;
      margin-bottom: 10px;
    }
    
    .product-price {
      font-size: 1.5rem;
      font-weight: bold;
      color: #007bff;
      margin-bottom: 20px;
    }
    
    .product-detail-card p {
      line-height: 1.6;
      color: #555;
    }
    
    .go-back-button {
      margin-top: 25px;
      padding: 10px 20px;
      background-color: #6c757d;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      font-size: 1rem;
      transition: background-color 0.2s ease;
    }
    
    .go-back-button:hover {
      background-color: #5a6268;
    }
    

    Now, try navigating to a product detail page! The content should update correctly based on the URL parameter.

Step 5: Basic Data Fetching (Simulated)

In a real application, product data wouldn’t be hardcoded. Let’s simulate data fetching using useState and useEffect to introduce the concept of loading states.

  1. Update src/pages/ProductDetail.jsx again: We’ll move the products data inside the component and simulate an asynchronous fetch.

    // src/pages/ProductDetail.jsx
    import React, { useState, useEffect } from 'react'; // Import useState and useEffect
    import { useParams, useNavigate } from 'react-router-dom';
    
    // Mock product data (could be from an API)
    const mockProducts = [
      { id: '1', name: 'Laptop Pro', price: 1200, description: 'Powerful laptop for professionals.', details: '16GB RAM, 512GB SSD, Intel i7' },
      { id: '2', name: 'Wireless Mouse', price: 25, description: 'Ergonomic mouse with long battery life.', details: 'Bluetooth 5.0, 1000 DPI, 2-year warranty' },
      { id: '3', name: 'Mechanical Keyboard', price: 90, description: 'Clicky keys for the best typing experience.', details: 'Cherry MX Blue switches, RGB backlight, full-size' },
      { id: '4', name: '4K Monitor', price: 350, description: 'Stunning visuals for work and play.', details: '27-inch IPS, 3840x2160 resolution, USB-C hub' },
    ];
    
    function ProductDetail() {
      const { productId } = useParams();
      const navigate = useNavigate();
    
      const [product, setProduct] = useState(null); // State to store the fetched product
      const [loading, setLoading] = useState(true); // State to track loading status
      const [error, setError] = useState(null); // State to track errors
    
      useEffect(() => {
        // Simulate an API call
        const fetchProduct = async () => {
          setLoading(true);
          setError(null);
          try {
            // Simulate network delay
            await new Promise((resolve) => setTimeout(resolve, 500));
            const foundProduct = mockProducts.find((p) => p.id === productId);
            if (foundProduct) {
              setProduct(foundProduct);
            } else {
              setError('Product not found.');
            }
          } catch (err) {
            setError('Failed to fetch product data.');
            console.error(err);
          } finally {
            setLoading(false);
          }
        };
    
        fetchProduct();
      }, [productId]); // Re-run effect when productId changes
    
      if (loading) {
        return (
          <div className="product-detail-card">
            <p>Loading product details...</p>
          </div>
        );
      }
    
      if (error) {
        return (
          <div className="product-detail-card">
            <h1>Error</h1>
            <p>{error}</p>
            <button onClick={() => navigate('/products')}>Go to Products</button>
          </div>
        );
      }
    
      if (!product) { // Should not happen if error is caught, but as a fallback
        return (
          <div className="product-detail-card">
            <h1>Product Not Found</h1>
            <p>The product with ID "{productId}" does not exist.</p>
            <button onClick={() => navigate('/products')}>Go to Products</button>
          </div>
        );
      }
    
      return (
        <div className="product-detail-card">
          <h1>{product.name}</h1>
          <p className="product-price">${product.price}</p>
          <p>{product.description}</p>
          <p><strong>Details:</strong> {product.details}</p>
          <button onClick={() => navigate(-1)} className="go-back-button">
            Go Back
          </button>
        </div>
      );
    }
    
    export default ProductDetail;
    
    • Explanation:
      • We introduce useState for product, loading, and error to manage the component’s state during data fetching.
      • The useEffect hook is used to perform the fetchProduct side effect. It runs after the initial render and whenever productId changes (specified in the dependency array [productId]).
      • Inside fetchProduct, we simulate an asynchronous operation with setTimeout and find the product.
      • We update loading and error states accordingly.
      • Before rendering the product details, we check loading and error states to display appropriate messages to the user. This is a common pattern for handling asynchronous data.

Now, when you navigate to a product detail page, you’ll briefly see “Loading product details…” before the actual content appears, simulating a real network request.

Mini-Challenge: Add a “Contact Us” Page

Your turn! Let’s extend our product viewer application.

Challenge: Add a new “Contact Us” page to the application.

  1. Create a new functional component for Contact.jsx in the src/pages directory. This component should display a simple heading like “Contact Us” and a paragraph.
  2. Add a new route for /contact in src/main.jsx that renders your Contact component.
  3. Add a new Link to the “Contact Us” page in your src/components/Navbar.jsx.

Hint:

  • You can copy one of the existing placeholder page components (like Home.jsx) as a starting point for Contact.jsx.
  • Remember to import your new Contact component in src/main.jsx before adding it to the createBrowserRouter configuration.

What to observe/learn: This exercise reinforces your understanding of how to add new routes and components, and how to integrate them into the navigation system of your SPA. You’ll see how easily react-router-dom scales to include more views.

Common Pitfalls & Troubleshooting

Even with simple routing and data flow, you might encounter some common issues:

  1. “Nothing renders when I click a link!”

    • Pitfall: Forgetting to wrap your entire application (or at least the part that uses routing) with RouterProvider (or BrowserRouter in older setups) in src/main.jsx. React Router needs this context to function.
    • Troubleshooting: Double-check src/main.jsx to ensure RouterProvider router={router} is correctly rendering your App component.
  2. "useParams returns undefined"

    • Pitfall: Mismatch between the parameter name in your Route path and the name you’re destructuring from useParams. For example, if your route is path: 'products/:prodId', but you try to const { productId } = useParams();.
    • Troubleshooting: Ensure the colon-prefixed name in your path prop (e.g., :productId) exactly matches the key you’re trying to access from the object returned by useParams.
  3. “Full page reload when clicking a link”

    • Pitfall: Using a regular <a> HTML tag instead of the Link component from react-router-dom. Standard <a> tags trigger a full browser navigation.
    • Troubleshooting: Replace all instances of <a href="..."> with <Link to="..."> for internal navigation.
  4. Prop Drilling:

    • Pitfall: Passing the same prop through many layers of components, even if intermediate components don’t directly use it. This makes your component tree rigid and hard to refactor.
    • Troubleshooting: While not a “pitfall” in terms of breaking your app, it’s an architectural anti-pattern. If you find yourself passing a prop more than 2-3 levels deep, consider using React’s Context API or a dedicated state management library (like Redux, Zustand, Jotai, Recoil) for that piece of data. We’ll explore these in later chapters!

Summary

Congratulations! You’ve successfully navigated the basics of client-side routing and fundamental data flow in React SPAs. Here’s a quick recap of what we covered:

  • Client-Side Routing: How SPAs provide a seamless user experience by dynamically updating content without full page reloads, leveraging the browser’s History API.
  • React Router v6: We learned to use createBrowserRouter and RouterProvider to set up our routing configuration.
  • Core Routing Components & Hooks:
    • Link for declarative navigation.
    • Routes and Route to define URL-to-component mappings, including dynamic parameters like :productId.
    • useParams to extract dynamic values from the URL.
    • useNavigate for programmatic navigation.
    • Outlet for rendering nested routes.
  • Basic Data Flow: We reinforced our understanding of:
    • Props: Passing immutable data from parent to child.
    • State: Managing mutable, component-specific data using useState.
    • Context API: Briefly introduced as a solution for sharing data across the component tree to avoid prop drilling.
    • Data Fetching: Simulated asynchronous data loading using useEffect and useState to manage loading and error states.

You now have the foundational knowledge to build multi-page React applications with interactive navigation and responsive data handling. In the next chapter, we’ll expand on data management, diving deeper into advanced state patterns and exploring how to manage complex application state more effectively.


This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.

References