1. The Async Data Lifecycle
Whenever a React component fetches records from an external backend API, the user interface moves through three distinct states:
- Loading: Request is in flight (render skeleton or spinner).
- Error: Request failed due to network error, 401 Unauthorized, or 500 Server Error.
- Success: Data arrived successfully (render content).
2. Fetching with Native fetch() API
import { useState, useEffect } from 'react';
function CourseList() {
const [courses, setCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState("");
useEffect(() => {
const controller = new AbortController(); // For request cancellation
async function loadCourses() {
try {
setIsLoading(true);
const res = await fetch("https://onlinecbt.in/ibs/api/technical-learning/courses", {
signal: controller.signal
});
if (!res.ok) throw new Error(`Server returned status ${res.status}`);
const json = await res.json();
setCourses(json.data || []);
} catch (err) {
if (err.name !== "AbortError") {
setErrorMessage(err.message);
}
} finally {
setIsLoading(false);
}
}
loadCourses();
// Cleanup: cancel request if component unmounts before response returns
return () => controller.abort();
}, []);
if (isLoading) return Loading courses...;
if (errorMessage) return Error: {errorMessage};
return (
{courses.map(c => - {c.name}
)}
);
}
3. Using Axios
Axios is widely preferred for production apps because it automatically serializes JSON, supports request/response interceptors (for injecting JWT Bearer tokens), and throws automatically on HTTP error statuses (4xx, 5xx).