This is useEffect's single most common real use: fetching data isn't something a component does during render (render must stay a pure calculation of JSX) — it's a side effect that happens because the component rendered, which is exactly what effects are for.
Three pieces of state — the data itself, whether it's still loading, and any error — plus a fetch call inside an effect, using the fetch() and async/await you already know from the JavaScript Fetch API lesson:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => setUser(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <h1>{user.name}</h1>;
}[userId] as the dependency matters specifically here: without it, the effect only fetches once, ever — switching to a different user's profile would keep showing the first user's data.
If userId changes again before the first request finishes, or the component unmounts entirely, the earlier request's result can still arrive and incorrectly update state that no longer applies. A cleanup flag (from the useEffect lesson) guards against this:
useEffect(() => {
let ignore = false;
setLoading(true);
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => {
if (!ignore) setUser(data); // skip if this effect is stale
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => {
ignore = true; // marks this specific request as stale
};
}, [userId]);The effect function itself can't be async (it would return a Promise instead of a cleanup function), so an inner function is the standard workaround:
useEffect(() => {
async function loadUser() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
setUser(data);
}
loadUser();
}, [userId]);