Lesson 8 — Fetching and async state¶
Objective¶
Introduce a topic repository with explicit loading, success, empty, and error states.
Prerequisites¶
Lesson 7 — Typed controlled forms and validation. You should be able to build controlled forms and validate user input.
Mental Model¶
A request is not just data; it is a state machine. Network success, HTTP success, valid payloads, and current-request relevance are separate questions.
Concept¶
Async code in the UI is a state machine with at least idle, loading, success, and error states. A repository boundary owns transport, parsing, and validation. Requests can race, so a newer response should not be overwritten by an older one. Keeping previous data visible during reload is a deliberate policy, not a default.
Example¶
type Async<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
async function fetchTopics(): Promise<Topic[]> {
const res = await fetch('/api/topics');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const payload = await res.json();
return validateTopics(payload);
}
Real-World Usage¶
Search-as-you-type, dashboards that poll, feeds that paginate, and any UI that talks to a remote system.
Common Mistakes¶
- Treating a
fetchrejection and a non-2xx response as the same failure. - Letting an old, slow request overwrite a newer one.
- Showing nothing during loading because "it is fast on my machine."
Mini Lab¶
Task¶
Define a repository interface and discriminated async state. Fetch topics, validate the response boundary, display retry UI, and keep previous data policy explicit.
Constraints¶
- Exercise slow, empty, malformed, non-2xx, offline, and retry paths.
- Assert visible behavior rather than implementation calls alone.
Expected Result¶
The UI displays a loading state, then either data, an empty message, or a recoverable error. Stale request results are ignored, and retries work without a full reload.
Hints¶
Keep transport parsing at the repository boundary. For a larger production app, evaluate a server-state library instead of rebuilding caching and deduplication.
Knowledge Check¶
- Why does
fetchnot reject every failed HTTP request? - How do stale requests overwrite newer results?
- Which concerns belong to server-state caching?
Challenge¶
A search input fires a request on every keystroke. Outline a request cancellation and debounce policy, and explain when you would keep previous results versus clear them.
Summary¶
Server state is a state machine. Own transport at a repository boundary and make request races, retries, and previous-data policies explicit.