Effects and essential hooks · Lesson 12 of 21
Fetching data
Loading, error and data, race conditions, and why libraries help.
Every API request has three states: loading, failed or loaded, and the UI must show all three. Here's the basic pattern of fetch inside useEffect (calling a public test API):
function App() {
const [users, setUsers] = useState([])
const [status, setStatus] = useState("loading")
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users?_limit=4")
.then((res) => {
if (!res.ok) throw new Error("HTTP " + res.status)
return res.json()
})
.then((data) => { setUsers(data); setStatus("done") })
.catch((err) => { console.error(err.message); setStatus("error") })
}, [])
if (status === "loading") return <p>Loading…</p>
if (status === "error") return <p>Couldn't load users.</p>
return <ul>{users.map((u) => <li key={u.id}>{u.name} · {u.email}</li>)}</ul>
}Race conditions
If the user switches quickly, an old response can arrive after the new one and overwrite it, showing the wrong data. The fix: in the cleanup, mark that request as no longer relevant, or cancel it with AbortController. This example uses a fake API with random delays; switch quickly between students:
const fakeApi = (id) =>
new Promise((resolve) => setTimeout(() => resolve({ id, xp: id * 100 }), Math.random() * 800))
function Profile({ id }) {
const [data, setData] = useState(null)
useEffect(() => {
let ignore = false // this request is still the latest one
setData(null)
fakeApi(id).then((d) => { if (!ignore) setData(d) })
return () => { ignore = true } // a newer id arrived: ignore this answer
}, [id])
return <p>{data ? "Student " + data.id + " has " + data.xp + " xp" : "Loading…"}</p>
}
function App() {
const [id, setId] = useState(1)
return (
<div>
{[1, 2, 3, 4].map((n) => <button key={n} onClick={() => setId(n)}>{n}</button>)}
<Profile id={id} />
</div>
)
}request id=1 (slow)
request id=2 (fast) → cleanup ignores the first
response 2 arrives and shows
response 1 arrives late and is ignored
Quick question
Why do we need ignore or AbortController in the cleanup?
Exercise
Use fakeCourses() (provided) to load the courses. Show Loading… first, then once they arrive show the count as 3 courses with their titles in a <ul> below.
3 courses HTML & CSS JavaScript React
const fakeCourses = () =>
new Promise((resolve) => setTimeout(() => resolve(["HTML & CSS", "JavaScript", "React"]), 150))
function App() {
return <p>Loading…</p>
}Learn more from
Played 0 of 2 games
Sign in to save your progress and earn XP.