A request is not instant
Requesting data from an API takes time: it travels over the network, the backend works, it comes back. During that time your interface cannot go blank. Every API call goes through three states and the UI must account for all three:
- Loading — the request went out, there is still no response. Show a spinner or a skeleton.
- Error — the response failed (network down,
404,500). Show a message and, if appropriate, a retry button. - Data — the correct response arrived. Render the content.
async function load(api) {
// state: loading
try {
const data = await api("/users");
return { state: "data", data }; // state: data
} catch (e) {
return { state: "error", error: e.message }; // state: error
}
}
Why try/catch matters
If you do not catch the error, a downed API breaks the interface: the promise is
rejected, the render fails, and the user sees a broken screen without knowing what happened.
Wrapping the call in try/catch turns a network failure into a state
you know how to paint.
Adapting the response to the UI
The shape the API returns is rarely the one your interface needs to paint.
It is good practice to adapt (map) the response to the shape the
UI consumes, in a single layer, instead of scattering user.personal_data.name
all over the application:
function adaptUser(u) {
return { id: u.id, name: u.full_name, active: u.status === "ACTIVE" };
}
That way, if the backend contract changes, you only tweak the adapter and the rest of the UI stays intact.
In the next module you will see the other piece of connecting front and back: the authentication with tokens (JWT), so the backend knows who makes each request.