Unit 5 - Notes

INT252

Unit 5: HTTP Methods and Routing

Part 1: HTTP Requests in React

Modern React applications often function as Single Page Applications (SPAs) that interact with backend APIs to manage data. This communication handles the CRUD (Create, Read, Update, Delete) lifecycle.

1. Fetch API vs. Axios

There are two primary ways to handle HTTP requests in React: the native fetch() API and the third-party axios library.

Fetch API

The Fetch API is built into modern browsers. It uses Promises to handle asynchronous operations.

  • Pros: Native (no installation required), lightweight.
  • Cons: Requires manual JSON parsing (response.json()), does not handle HTTP error codes (404/500) automatically in the catch block, verbose syntax for complex requests.

Syntax:

JAVASCRIPT
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

Axios

Axios is a Promise-based HTTP client for the browser and Node.js.

  • Installation: npm install axios
  • Pros: Automatic JSON data transformation, automatic error handling for HTTP 4xx/5xx statuses, request/response interceptors, straightforward syntax for sending data.
  • Cons: Adds a small bundle size overhead to the project.

Syntax:

JAVASCRIPT
import axios from 'axios';

axios.get('https://api.example.com/data')
  .then(response => console.log(response.data))
  .catch(error => console.error(error));


2. HTTP Methods Implementation

In React, these requests are typically handled inside the useEffect hook (for fetching on load) or event handlers (for user actions).

GET Requests (Read)

Used to retrieve data from a server.

Using Axios:

JAVASCRIPT
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const UserList = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    // Empty dependency array ensures this runs only once on mount
    axios.get('https://jsonplaceholder.typicode.com/users')
      .then(res => setUsers(res.data))
      .catch(err => console.error("Error fetching data:", err));
  }, []);

  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
};

POST Requests (Create)

Used to send data to the server to create a new resource. Requires a request body and headers.

Using Axios:

JAVASCRIPT
const addUser = () => {
  const newUser = { name: "John Doe", email: "john@example.com" };

  axios.post('https://jsonplaceholder.typicode.com/users', newUser)
    .then(res => {
      console.log("User created:", res.data);
      // Logic to update local state often follows here
    })
    .catch(err => console.error(err));
};

PUT Requests (Update)

Used to update an existing resource. Typically requires the ID of the item to update and the new data payload.

Using Axios:

JAVASCRIPT
const updateUser = (id) => {
  const updatedData = { name: "Jane Doe" };

  axios.put(`https://jsonplaceholder.typicode.com/users/${id}`, updatedData)
    .then(res => console.log("Update successful:", res.data))
    .catch(err => console.error(err));
};

DELETE Action (Delete)

Used to remove a resource from the server. Usually requires the ID of the resource.

Using Axios:

JAVASCRIPT
const deleteUser = (id) => {
  axios.delete(`https://jsonplaceholder.typicode.com/users/${id}`)
    .then(res => {
      console.log("Deleted successfully");
      // Logic to remove item from local UI state
    })
    .catch(err => console.error(err));
};


Part 2: Routing with React Router

Routing allows a React application to simulate navigation between different pages while actually remaining on a single HTML page (SPA architecture). The standard library is react-router-dom.

Prerequisites: npm install react-router-dom

1. Setting up Routing and Routes

The setup involves three main components:

  1. BrowserRouter: Wraps the application and enables routing context.
  2. Routes: Acts as a container for all route definitions.
  3. Route: Defines a specific path and the component to render.

App.js Configuration:

JAVASCRIPT
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Contact from './Contact';
import NotFound from './NotFound';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* Exact path matches */}
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        
        {/* Wildcard route for 404 pages */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

2. Navigating to Pages (Declarative)

Do not use standard HTML <a href="..."> tags, as they cause the page to refresh and lose React state. Instead, use the <Link> component.

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

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

3. Navigating to Pages (Programmatic)

Sometimes you need to navigate after an action (e.g., submitting a form or logging in). For this, use the useNavigate hook.

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

const Login = () => {
  const navigate = useNavigate();

  const handleLogin = () => {
    // ... perform authentication logic
    
    // Redirect to Dashboard upon success
    navigate('/dashboard');
    
    // OR Redirect and replace history (prevent going back to login)
    // navigate('/dashboard', { replace: true });
  };

  return <button onClick={handleLogin}>Log In</button>;
};

4. Navigating Back and Next Page

The useNavigate hook also handles history traversal.

JAVASCRIPT
const HistoryControls = () => {
  const navigate = useNavigate();

  return (
    <div>
      {/* Go back one step in history */}
      <button onClick={() => navigate(-1)}>Go Back</button>
      
      {/* Go forward one step in history */}
      <button onClick={() => navigate(1)}>Go Forward</button>
    </div>
  );
};


Part 3: Data Handling in Routes

React Router offers multiple mechanisms to pass data between components during navigation.

1. URL Parameters (Dynamic Routing)

Used for identifying specific resources (e.g., a specific user profile).

Route Definition:

JAVASCRIPT
<Route path="/users/:id" element={<UserProfile />} />

Accessing the Parameter (useParams):

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

const UserProfile = () => {
  const { id } = useParams(); // Captures the :id part of the URL
  return <h1>Displaying User ID: {id}</h1>;
};

2. Passing Data via Query Params

Query parameters allow passing optional data (e.g., filtering, sorting) in the URL string (e.g., /search?q=react&sort=asc).

Accessing Query Params (useSearchParams):

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

const SearchPage = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const query = searchParams.get('q'); // 'react'
  const sort = searchParams.get('sort'); // 'asc'

  const updateSort = () => {
    // Update URL to ?q=...&sort=desc
    setSearchParams({ q: query, sort: 'desc' });
  };

  return <div>Searching for: {query}</div>;
};

3. Passing Data Between Pages (State Object)

You can pass complex objects (arrays, objects) via the router's state without showing the data in the URL.

Sending Data:

JAVASCRIPT
// Via Link
<Link to="/details" state={{ userId: 1, role: 'admin' }}>View Details</Link>

// Via useNavigate
navigate('/details', { state: { userId: 1, role: 'admin' } });

Receiving Data (useLocation):

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

const Details = () => {
  const location = useLocation();
  const data = location.state; // { userId: 1, role: 'admin' }

  return <div>User Role: {data?.role}</div>;
};

4. Fetching Data (Integration Strategy)

Combining Routing and HTTP requests creates dynamic pages. The standard pattern is to read the ID from the URL, then fetch the specific data for that ID.

Comprehensive Example:

JAVASCRIPT
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import axios from 'axios';

const ProductDetail = () => {
  const { id } = useParams(); // Get ID from URL
  const navigate = useNavigate();
  
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  useEffect(() => {
    const fetchProduct = async () => {
      try {
        setLoading(true);
        const response = await axios.get(`https://api.store.com/products/${id}`);
        setProduct(response.data);
      } catch (err) {
        setError('Product not found');
      } finally {
        setLoading(false);
      }
    };

    fetchProduct();
  }, [id]); // Re-run if ID changes

  if (loading) return <div>Loading...</div>;
  if (error) return <div>{error} <button onClick={() => navigate(-1)}>Back</button></div>;

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>Price: ${product.price}</p>
    </div>
  );
};

export default ProductDetail;