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 thecatchblock, verbose syntax for complex requests.
Syntax:
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:
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:
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:
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:
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:
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:
BrowserRouter: Wraps the application and enables routing context.Routes: Acts as a container for all route definitions.Route: Defines a specific path and the component to render.
App.js Configuration:
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.
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.
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.
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:
<Route path="/users/:id" element={<UserProfile />} />
Accessing the Parameter (useParams):
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):
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:
// 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):
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:
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;