You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Most real-world React applications need to retrieve data from an API. The standard browser Fetch API combined with useEffect and useState gives you everything you need to load, display, and manage remote data.
import { useEffect, useState } from "react";
function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => {
if (!res.ok) throw new Error("Network response was not ok");
return res.json();
})
.then((data) => {
setPosts(data);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
The three state variables — posts, loading, and error — cover the three possible UI states: loading, error, and success.
The same fetch can be written more cleanly with async/await inside a nested function (because the effect callback itself cannot be async):
useEffect(() => {
async function loadPosts() {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!res.ok) throw new Error("Failed to fetch");
const data = await res.json();
setPosts(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadPosts();
}, []);
When the data you need depends on a prop (like a user ID), include that prop in the dependency array so the fetch re-runs whenever it changes:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
setUser(null); // clear previous user while loading
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then(setUser);
}, [userId]);
if (!user) return <p>Loading...</p>;
return <h2>{user.name}</h2>;
}
When a prop changes quickly, multiple requests can be in-flight at the same time. Use a cancelled flag or AbortController to ignore stale responses:
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then((res) => res.json())
.then(setUser)
.catch(() => {}); // AbortError is expected on cleanup
return () => controller.abort();
}, [userId]);
To create or update resources, use fetch with a method and body:
async function createPost(title) {
const res = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
});
return res.json();
}
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.