Loading & Error States
Part of the Full Stack Developer Roadmap
In short
A blank screen for three seconds feels broken, even if the data is on its way. Clear loading, empty and error screens are what make an app feel trustworthy.
Designing what your UI shows while waiting for the backend, and when something goes wrong — the difference between an app that feels broken and one that feels polished.

Think of it like this
Platform announcements
A good station tells you 'train arriving on platform 3', 'running 20 minutes late' or 'cancelled, take the next one'. A bad station says nothing and you stare at the tracks. Your UI is the station: announce every state and never leave the user guessing.
Watch it happen
One request, every screen
1/6Nothing has been asked yet. A search box might be waiting for input.
The big ideas
What you need to know
Model the states explicitly
Use one status value: 'loading', 'error' or 'success'. Separate isLoading and isError booleans can contradict each other.
Empty is not an error
Zero results is a valid answer. Show 'No orders yet' with a next step, not a red error box.
Errors in human words
Show 'Couldn't load orders. Check your connection.' Keep stack traces in the console and your logs.
Avoid spinner flashes
For quick responses, a skeleton shaped like the final layout feels smoother than a spinner that flickers for 100 ms.
See it in code
Walk through the code
1type State<T> =2 | { status: "loading" }3 | { status: "error"; message: string }4 | { status: "success"; data: T };5 6function Orders({ state, retry }: { state: State<Order[]>; retry: () => void }) {7 if (state.status === "loading") return <OrdersSkeleton />;8 if (state.status === "error")9 return (10 <p role="alert">11 Couldn't load orders. <button onClick={retry}>Try again</button>12 </p>13 );14 if (state.data.length === 0) return <p>No orders yet. Your first one is a click away.</p>;15 return <ul>{state.data.map((o) => <li key={o.id}>{o.item}</li>)}</ul>;16}1/4Exactly one state at a time. Impossible combos like 'loading and error' can't be written.
With a union type, TypeScript won't let you read state.data until you've checked the status is 'success'.
Save yourself the pain
Common mistakes
Oops
Showing a spinner forever when the request fails.
Do this instead
Always end in success or error. Catch failures, add a timeout, and offer a retry button.
Oops
Treating an empty list as an error.
Do this instead
Empty is success with no data. Show a friendly empty state that tells the user what to do next.
Your mission
Give every state a screen
Quick check
Your API returns 200 with an empty array. Which screen should the user see?
Go deeper
- 1Add explicit loading, error and empty states to every data-fetching component.
- 2Build a reusable skeleton loader component reused across your app.
- 3Design user-friendly error messages instead of showing raw error objects.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.