Error, Empty, and Retry States: Core Concepts
Model request states explicitly
A data view has more states than loaded or not loaded. A useful model distinguishes initial idle, pending, success with results, success with no results, and failure. A refresh may also have stale data while a new request is pending. Explicit states prevent a blank page from ambiguously meaning empty, loading, unauthorized, or broken.
Keep request data, pending state, and error state separate or use a discriminated status union. Define which states can transition to each other. When a new request begins, clear the prior error or associate it with the previous query deliberately. When a request succeeds with an empty array, render an empty state rather than a loading indicator. When it fails, preserve previous data only if the UI labels it as stale and actions remain safe.
Distinguish network and HTTP errors
Fetch rejects for transport failures such as DNS or offline conditions, but resolves for HTTP responses like 404 or 500. Check response.ok or status before parsing the success shape. JSON parsing can also fail and should be handled. Map server status and error codes into stable UI categories; do not display raw HTML or internal stack traces to users.
An abort can be intentional because the user navigated away or a newer request superseded the old one. It should not necessarily render as an error. Track the request identity or signal and ignore cancellation from obsolete work. A timeout is different: it may represent a recoverable service failure and should be communicated with a retry option when appropriate.
Design useful empty and error states
An empty state should explain that the request succeeded but returned no records and offer an action if one exists, such as changing filters or creating the first item. An error state should state what failed at a useful level and what the user can do next. Avoid generic "Something went wrong" without retry or support context. Preserve form inputs after a failed save so users do not lose work.
Make state changes accessible. Use a status region for loading messages, associate field errors with their controls, and move focus to an error summary only when the workflow requires it. Do not communicate state solely through color. Avoid announcing every background refresh if it would create noisy screen-reader output; announce meaningful transitions.
Retry with care
A retry function should reuse the request logic and current parameters, but not blindly repeat non-idempotent writes. Reads are usually safe to retry with bounded backoff. For writes, use an idempotency key or operation status if the server may have committed before the client timed out. Disable or label retry while an attempt is active to prevent overlapping requests.
Retries can amplify an outage. Use a limit, delay, and jitter for automatic retries, and let the user initiate a new attempt for recoverable foreground errors. Preserve enough context for support, such as a correlation id, while avoiding sensitive payloads. Monitor retry rate and failure class so a repeated error does not become an invisible loop.
The view should always make its current state understandable. Separate loading, empty, stale, and error presentation, and define the transition behavior for each request. A complete state model improves both user experience and test coverage.
Error, Empty, and Retry States: Practice
Build a resource state machine
Create a list view with data, pending, error, and a retry function. On initial load, show a progress state. On success with records, show the list. On success with an empty array, show an empty state with a useful next action. On failure, show a concise error and a retry button. Keep these conditions mutually understandable; do not render an empty message merely because data has not arrived yet.
For refresh, decide whether the previous list remains visible. If it does, show a small refreshing indicator and label the data as potentially stale. If it is cleared, keep the page structure stable to avoid layout shifts. A background refetch failure may preserve old data and show a non-blocking warning, while an initial failure needs a full error state.
Map failure classes
Handle network rejection, HTTP status failure, invalid JSON, and abort separately. An abort from navigation should not produce a user-facing error. A 404 can become a not-found view; 401 may initiate authentication; 403 should explain access denial without exposing protected data. A 500 or timeout can offer retry. Keep server details in protected telemetry and expose a safe correlation id when support needs it.
Retrying a read can use the same function with the current filter or route id. Guard against duplicate clicks while pending. For a write, do not assume a timeout means the server did nothing; use an idempotency key or fetch operation status. Preserve user input and show whether the action is still processing or safe to resubmit.
Accessibility and testing
Use aria-live status text for loading or background refresh when announcement is useful. Link errors to fields with aria-describedby and set aria-invalid. A page-level failure may move focus to a summary, but repeated retries should not unexpectedly steal focus. Keep color as a secondary cue, not the only indicator. Use visible button labels such as Retry loading projects rather than a generic icon without accessible text.
Test each state by controlling the request promise. Resolve with data, resolve with an empty array, reject with network failure, return a non-success status, and abort after navigation. Test refresh with stale data, repeated retry clicks, and a write timeout. Assert what the user can see and operate, not only the internal refs. Verify the retry action uses the current query and does not duplicate a committed write.
Prevent stale responses
Start a request for one filter, change the filter, and start another. Resolve the first request last. The old result must not overwrite the current view. Use AbortController or a monotonically increasing request token. Keep loading state tied to the active request so an old finally block cannot clear a newer pending indicator.
The result is a view that communicates state honestly. An empty result is not an error; a canceled request is not necessarily a failure; a timeout does not prove a write was rolled back. Naming these distinctions in code makes UI behavior predictable and tests comprehensive.
Error, Empty, and Retry States: Trade-offs and Interviews
Preserve context without presenting stale data as current
Keeping old data during refresh reduces flicker and lets users continue reading, but it can mislead if the new query or route represents a different resource. Label stale content and disable actions that could operate on the wrong entity. Clearing content makes the transition explicit but can create a blank screen. Choose based on whether continuity or correctness risk matters more.
Retries improve recovery from transient failures but can worsen overload. Use bounded automatic retries with backoff and jitter, and offer manual retry for foreground failures. A canceled request should not create a red error state. A timed-out write needs idempotency or status reconciliation because the server may already have completed it.
State design guides accessibility and tests
Separate initial loading, background refresh, empty success, not found, access denied, and generic failure when their recovery differs. Do not overload one boolean with multiple meanings. Announce important status changes without flooding assistive technology. Keep retry and empty-state actions keyboard accessible and preserve user-entered form data after failure.
A discriminated union can make impossible combinations harder to represent, while separate refs may be more convenient for stale data plus pending state. Choose a model that expresses the real transitions and is testable. Use controlled promises to verify race conditions, abort behavior, and exact visible states.
In an interview, distinguish an empty success from an error, describe HTTP status handling, explain cancellation versus timeout, and show a safe retry path. Mention stale responses and accessibility. This demonstrates that loading states are part of data correctness, not just visual polish.
Retries should be scoped and observable. Show whether an attempt is in progress, prevent duplicate activation, and stop after a defined policy for automatic retries. Exponential backoff with jitter can reduce synchronized retries, but a user-triggered retry may use a simpler one-shot attempt. Record a safe error category and correlation id so support can find diagnostics without exposing the payload.
Preserving stale data is useful only when the view identifies which query produced it. If a filter changes, old results should not appear as if they match the new filter. Keep a request key beside the data and render a stale label or clear it until the new request succeeds. For sensitive data, clear it on logout or identity change rather than showing it during a new session's loading state.
An empty state should explain why there are no results and what action is possible. A no-permission response is not an empty list, and a failed request is not zero records. Keep these states distinct in both code and tests. Use response types or a discriminated status model so rendering cannot accidentally treat a failed parse as successful empty data.
In an interview, explain the state transitions and what remains visible after failure. Give an example where retry is unsafe because a write may have committed. Discuss accessible status messaging and a stale-response guard. This shows a loading UI communicates data correctness and recovery, not just animation.
Quiz
1. Which statement about Error, Empty, and Retry States is accurate?
2. When reviewing Error, Empty, and Retry States, which point must hold?
3. How does fetch represent an HTTP 500 response?
4. Why distinguish an empty success from a failed request?
Flashcards
Question
What is Error, Empty, and Retry States?
Click to reveal answer
Answer
Render explicit states for failure, no results, and user-triggered retries.
Question
Error, Empty, and Retry States: why it matters
Click to reveal answer
Answer
A dedicated error ref drives an inline message instead of a silent console log.
Question
Error, Empty, and Retry States: key detail
Click to reveal answer
Answer
Empty result sets deserve their own branch distinct from loading and error states.
Question
Error, Empty, and Retry States: practical note
Click to reveal answer
Answer
A retry function stored in setup can be re-invoked by a button without refetching logic duplication.
Question
How does fetch represent an HTTP 500 response?
Click to reveal answer
Answer
Fetch rejects for transport errors, while HTTP error statuses still resolve and must be checked.
Revision Notes
Key Takeaways
- 1. A dedicated error ref drives an inline message instead of a silent console log.
- 2. Empty result sets deserve their own branch distinct from loading and error states.
- 3. A retry function stored in setup can be re-invoked by a button without refetching logic duplication.
Interview Tips
- • Explain Error, Empty, and Retry States with one concrete example from real vue work, not a textbook restatement.
- • Discuss the trade-off: when Error, Empty, and Retry States helps and when a lighter approach wins.
- • Place Error, Empty, and Retry States inside the HTTP Clients and Data Fetching phase — interviewers check that you see the progression, not isolated facts.
Cheat Sheet
Error, Empty, and Retry States Cheat Sheet
Phase: HTTP Clients and Data Fetching | Level: intermediate
Key points:
- A dedicated error ref drives an inline message instead of a silent console log.
- Empty result sets deserve their own branch distinct from loading and error states.
- A retry function stored in setup can be re-invoked by a button without refetching logic duplication.
- Timeouts should surface as errors too, since aborted requests reject the underlying promise.
Tags: vue, http, ux
Continue Learning
Before You Start
- No prerequisites listed.