Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How should a page distinguish loading, empty, and failed data?

Quick answer

Represent the request with four mutually exclusive states: loading, success with items, success with no items, and error. Show a loading indicator while waiting, an empty-state message only after a successful response contains no items, the collection when items exist, and an error message if the request fails. Keeping these states separate prevents a network problem from being mistaken for an empty collection.

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I’m building a page that fetches a list. How do I decide whether to show a spinner, an empty message, the list, or an error?

Geez

Treat the result of the request as one of four states, rather than inferring everything from whether the list happens to contain items. A useful state map is:

  1. Loading: the request is in progress. Show a progress message or indicator; don’t label the collection empty yet.
  2. Success with no items (empty): the request completed successfully and returned an empty collection. Show an empty-state message, such as “No projects yet.”
  3. Success with items: the request completed successfully and returned one or more items. Show the collection.
  4. Error: the request did not complete successfully. Explain that the list could not be loaded and, if available, offer a retry.

The important distinction is that an empty state is a successful result. A failed request means the page does not know whether items exist. Keep each state explicit so an initial empty array or a failed request can’t accidentally render the empty-state message.

Follow-up question

What should the state data look like? I currently keep items and loading separately.

Geez

Separate variables can accidentally describe contradictory situations—for example, loading is true while an old error is still present. One option is a single state value that says what the request means now. For example, in JavaScript:

const state = { status: "loading" };

// After a successful response:
const nextState = items.length === 0
  ? { status: "success", items: [] }
  : { status: "success", items };

// If the request fails:
const failedState = { status: "error", message: "Could not load projects." };

The status controls which kind of information belongs in that state. In a typed application, you can define a discriminated union so that success carries items and error carries its error information. Even without types, follow that rule consistently. Make “not loaded yet” different from “loaded successfully and empty”; don’t use an empty array to mean both.

Follow-up question

Can you show a small rendering example, including what happens if the request fails?

Geez

Here is a framework-neutral JavaScript rendering sketch. renderMessage and renderList stand for your own rendering functions; the example assumes items is an array after a successful response.

function render(state) {
  switch (state.status) {
    case "loading":
      return renderMessage("Loading projects…");

    case "success":
      return state.items.length === 0
        ? renderMessage("No projects yet.")
        : renderList(state.items);

    case "error":
      return renderMessage("Projects could not be loaded.");

    default:
      throw new Error("Unknown project-list state");
  }
}

A request handler can set loading before it starts, success only after a successful response, and error when the request fails. If there is a retry control, its action should start a new request and return the interface to loading. Avoid showing a reassuring empty-state message on failure; it can mislead people into thinking there really are no projects. This sketch doesn’t define a particular framework, network client, or project-specific retry behavior.

Follow-up question

How can I check that the four states stay distinct as the feature grows?

Geez

Walk through one example for each state and ask what a person should see. For loading, use the moment before a response arrives. For empty, use a successful response with []. For success, use a successful response with at least one item. For error, use a rejected request or other failure path. Then check transitions: loading should lead to success or error; a retry should lead from error back to loading. If the product supports refreshing existing data, decide deliberately whether to keep showing that data while refreshing or replace it with a loading view. That’s an additional design choice, not a reason to treat a failure as empty.

A copyable review note for a simple first-load collection is:

loading → show progress success + [] → show empty message success + items → show list error → show failure message and retry if supported

As a final check, confirm every message matches what the application actually knows. A failed request cannot establish that the collection has no items.

A practical checklist

  • Set an explicit loading state before starting the request.

  • Set empty only after success with an empty collection.

  • Render the collection only after success with items.

  • Render an error state when the request fails; don’t substitute an empty state.

  • Check each state and the error-to-loading retry transition.

  • Decide whether existing data remains visible during a refresh.

A few more useful answers

Can I use the same message for an empty result and a failed request?

It is better to distinguish them. Empty means the request succeeded and found no items; error means the page could not determine the result.

Should an empty array be the initial value?

It can be stored internally, but don’t use it alone to decide that the empty state should appear. Track whether the request is loading, has succeeded, or has failed.

What if a refresh fails but the page already has items?

Choose deliberately whether to retain the existing list and show a refresh error, or replace it with a full error state. In either case, don’t describe the collection as empty unless a successful result was empty.

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work