Fetch Users from API
Fetch and display a list of users from a REST API with loading and error states.
Live preview
Users
Code walkthrough
The bread-and-butter of real apps: call an API, handle the loading and error
states, then render the result. This version uses useEffect and the fetch API with
an AbortController for clean cancellation. The data comes from a same-origin static
endpoint (/api/users.json), so there's no third-party dependency — swap the URL for any
real REST API and the rest of the code is unchanged. Use View full code above for the
complete component.
1. Model the request as a status
Instead of separate loading/error booleans, a single status union makes the
states mutually exclusive — you can never be "loading" and "error" at once.
const [users, setUsers] = useState<User[]>([]);
const [status, setStatus] = useState<"loading" | "success" | "error">("loading");2. The fetch, with cancellation
load accepts an optional AbortSignal. We check res.ok, parse JSON, and swallow
the AbortError that fires when a request is cancelled (that's expected, not a real
failure).
const load = useCallback(async (signal?: AbortSignal) => {
try {
const res = await fetch("/api/users.json", { signal });
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
setUsers(await res.json());
setStatus("success");
} catch (err) {
if ((err as Error).name !== "AbortError") setStatus("error");
}
}, []);3. Fetch on mount, abort on unmount
The effect creates a controller, kicks off the request, and returns
controller.abort() for cleanup — so navigating away mid-flight never sets state on
an unmounted component.
useEffect(() => {
const controller = new AbortController();
load(controller.signal);
return () => controller.abort();
}, [load]);Always clean up in-flight requests. Without the abort, a slow response that resolves after unmount triggers a state update on a gone component.
4. Rendering each state
The JSX branches on status: a skeleton list while loading, a retry card on error,
and the user list on success. Open the full code to see the skeleton and list markup.
Related examples
Debounced Search in JavaScript
Implement a debounced search input with vanilla JavaScript.
View example