Fetching REST APIs with Async Await and State0%

Fetching REST APIs with Async Await and State

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Fetching REST APIs with Async Await and State

1. REST API Integration Architecture in React

In Single-Page Applications, data does not live inside the HTML bundle. Instead, client-side React communicates asynchronously with backend REST APIs over HTTP using JSON serialization.

A production-grade data fetching workflow must manage:

  1. 1
    The Data Container: Storing the parsed JSON payload.
  2. 2
    The Loading State: Displaying skeletons or spinners while the network request is pending.
  3. 3
    The Error State: Catching network dropouts, non-200 HTTP responses, or JSON parse failures gracefully.
  4. 4
    The Cleanup Guard: Preventing state updates if the component unmounts before the HTTP request resolves.
Output
[Component Mounts]
│
▼
[Dispatch fetch()] ──> [status = 'loading'] ──> [Render Loading Spinner]
│
├─ Success (HTTP 200) ──> [data = payload, status = 'idle'] ──> [Render Data List]
└─ Failure (HTTP 500) ──> [error = err, status = 'error'] ──> [Render Error Alert]

2. Implementing Clean Async Data Fetching

In React, useEffect cannot accept an async function directly. The standard pattern defines an async function inside the effect and executes it:

JSX
import React, { useState, useEffect } from 'react';
 
export default function CourseDirectory() {
const [courses, setCourses] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
 
useEffect(() => {
let isMounted = true; // Cleanup flag against race conditions
 
async function loadCourses() {
setLoading(true);
setError(null);
 
try {
const response = await fetch('/api/courses');
 
// CRITICAL: fetch() does NOT reject on HTTP 404 or 500!
if (!response.ok) {
throw new Error(`Server returned HTTP ${response.status}: ${response.statusText}`);
}
 
const data = await response.json();
 
if (isMounted) {
setCourses(data);
}
} catch (err) {
if (isMounted) {
setError(err.message || 'An unexpected error occurred');
}
} finally {
if (isMounted) {
setLoading(false);
}
}
}
 
loadCourses();
 
return () => {
isMounted = false; // Teardown
};
}, []); // Run on initial mount
 
if (loading) return <div className="spinner">Loading engineering courses...</div>;
if (error) return <div className="error-box">Error loading data: {error}</div>;
 
return (
<div className="course-grid">
{courses.map(c => (
<div key={c.id} className="course-card">
<h4>{c.title}</h4>
<p>{c.description}</p>
</div>
))}
</div>
);
}

3. The fetch() response.ok Trap

A critical misunderstanding among JavaScript engineers: The native window.fetch() Promise only rejects on complete network failures (e.g. offline, DNS failure).

If a backend server returns HTTP 404 Not Found or HTTP 500 Internal Server Error, fetch() still resolves successfully!

You must explicitly check if (!response.ok) to verify that the HTTP status code is in the 200–299 range before attempting to parse response.json().


Interactive Quiz

Practice Quiz

Question 1
Why doesn't a standard try...catch block around fetch() catch an HTTP 404 or 500 response automatically?
Question 2
Why is the async keyword placed on an internal helper function rather than directly on useEffect (e.g. useEffect(async () => ...) is invalid)?
Question 3
What is the purpose of the let isMounted = true boolean flag in the effect?
Question 4
Which HTTP header informs the client that the server is transmitting JSON data?
Question 5
What is the benefit of placing setLoading(false) inside a finally block?
Next Lesson

Handling HTTP POST, PUT, and DELETE Mutations

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext