Intermediate · Connecting frontend and backend · topic 19 of 34 · 30 min
Loading, errors and slow networks
Explanation
Three pieces of state
While a request is on its way, the screen must know three things: is it loading, did it fail (error), and what came back (data). In React each one is a piece of state. try/catch/finally fits this well: try does the request, catch stores the error, and finally runs in both cases, so it is the right place to turn loading off.
function NotesList() {
const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function load() {
setLoading(true);
setError(null);
try {
const res = await fetch('/api/notes', { signal: controller.signal });
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
setNotes(data.notes);
} catch (err) {
if (err.name !== 'AbortError') setError(err);
} finally {
if (!controller.signal.aborted) setLoading(false);
}
}
load();
return () => controller.abort(); // leaving the page cancels the request
}, []);
if (loading) return <p>Loading notes...</p>;
if (error) return <ErrorBanner error={error} />;
return <NoteGrid notes={notes} />;
}Two kinds of failure
| Network error | HTTP error | |
|---|---|---|
| What happens | fetch rejects (throws), often TypeError: Failed to fetch | fetch resolves, but res.ok is false |
| Typical causes | No internet, server down, CORS blocked, timeout | 400 bad input, 401 not logged in, 404, 500 server bug |
| Is there a body? | No answer at all | Yes, usually { error: { code, message } } |
| What to tell the user | "You seem to be offline" or "Taking too long" | The server's message, or a friendly text for the status |
Timeouts and cancelling
fetch has no short timeout of its own; on a weak signal a request can hang for a long time. An AbortController is an object with a signal you pass to fetch; calling controller.abort() cancels the request, and fetch rejects with an error named AbortError. AbortSignal.timeout(ms) is a ready-made signal that cancels itself after ms milliseconds; its error is named TimeoutError.
try {
const res = await fetch('/api/notes', { signal: AbortSignal.timeout(8000) });
// ... check res.ok and read the JSON
} catch (err) {
if (err.name === 'TimeoutError') showMessage('The server is taking too long. Please try again.');
else showMessage('You seem to be offline. Check your internet and try again.');
}Retry, but only when it is safe
Some failures are temporary: a network blip, or a 502, 503 or 504 while the server restarts. Retrying a GET is safe, because reading the notes twice changes nothing. Retrying a 4xx is pointless: the request itself is wrong, so the answer will be the same. Wait longer before each new try (500 ms, then 1 s, then 2 s), which is called exponential backoff, so a struggling server is not flooded.
// Create the key ONCE when the user presses Pay, and reuse it for any retry
const key = crypto.randomUUID();
await fetch('/api/payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': key },
body: JSON.stringify({ amount: 499, upiId: 'asha@okbank' }),
});Friendly messages and double submits
| Situation | Message to show |
|---|---|
| Network error | You seem to be offline. Check your internet and try again. |
| Timeout | The server is taking too long. Please try again. |
| 400 with a message | The server's message, such as "Title is required", next to the field |
| 401 | Please log in again. (then go to the login page) |
| 404 | This note was not found. It may have been deleted. |
| 500 to 599 | Something went wrong on our side. Please try again. |
A double submit happens when an impatient user taps Save twice on a slow network and two notes are created. Disable the Save button while saving, change its text to "Saving...", and enable it again only when the answer (or an error) arrives.
Watch the lesson
Loading, errors and slow networks · 1/7
Real networks are slow and sometimes fail. Every screen must handle that, not just the happy path.
Prefer a video by a teacher? Find free videos on “react fetch loading error state abortcontroller retry tutorial” on YouTube ↗
In real life: Booking a train ticket at a busy hour
At the busy morning booking hour, a train booking site is very slow. A good app shows a clear "Please wait, do not press back" message instead of a frozen screen, and the Book button is disabled after the first tap. Search results (GET) can be retried safely. The booking itself (POST) is not retried blindly: if the answer is lost, the app checks the booking status with the request id it already has, so the passenger is not charged twice. A UPI app does the same with a pending payment: it shows "Payment pending" and checks the status later rather than paying again.
Practice (0/3 done)
Write friendlyMessage(problem). problem is either an HTTP status number or an Error object. Rules, in this order: an Error whose name is "TimeoutError" gives "The server is taking too long. Please try again."; any other Error gives "You seem to be offline. Check your internet and try again."; 401 gives "Please log in again."; 404 gives "This note was not found."; 429 gives "Too many tries. Please wait a minute."; 500 to 599 gives "Something went wrong on our side. Please try again."; anything else gives "Something went wrong."
Write async fetchWithRetry(fn, retries, wait). fn() makes one request: it returns a response-like object { ok, status } or throws on a network error. Call fn(). If it throws, or the status is 500 or more, try again, up to retries more times. Before retry number n (1, 2, 3...) call await wait(100 * 2 ** (n - 1)), so the waits are 100, 200, 400 ms (exponential backoff). Return the first response with a status below 500 at once (never retry a 4xx). If every try fails, return the last response if the last try gave one, or throw the last error if it threw. (wait is passed in so the tests do not need real timers.)
Make the Notes list and the note form in your React app ready for slow and failing networks. Then test with DevTools throttling ("Slow 4G" and "Offline") and with the Express server stopped.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.