A merchant dashboard can look polished with a populated table and still be confusing in everyday use. The first request may be slow, a filter may return nothing, or a user may switch stores while an earlier response is still in flight. Those moments are part of the interface, too.

Name the states before building the table

Separate initial loading from background refresh. An empty store is different from a filter with no matches. A failed request is different from a successful request that returned an empty array. Keeping these distinctions explicit gives every message and control a clear purpose.

Protect the current context

If request A starts before request B but finishes after it, blindly assigning both results can replace the latest view with older data. Cancellation can reduce unnecessary work, while a request identity prevents an obsolete result from updating the interface.

let latestRequest = 0;

async function loadOrders(filters) {
  const request = ++latestRequest;
  loading.value = true;
  error.value = null;
  try {
    const result = await api.orders(filters);
    if (request === latestRequest) orders.value = result;
  } catch (cause) {
    if (request === latestRequest) error.value = cause;
  } finally {
    if (request === latestRequest) loading.value = false;
  }
}

This simplified Vue-style example assumes the state variables are refs. It handles response ordering, but does not cancel requests or define what to show when the store changes. Clear data that belongs to a previous tenant before presenting the new context; preserving a table is useful only when that table remains relevant.

Make the interaction usable without a mouse

Use real buttons for actions and associated labels for inputs. Tables should have meaningful column headers. Keep focus visible, and check the filter-to-results flow with the keyboard. A loading announcement can use a restrained live region; do not announce every row on every refresh.

When opening a detail view, think about where focus goes and how the user returns. Motion should help explain a change, and a reduced-motion preference should be respected. Color can reinforce a status, but the status also needs readable text.

Let the backend contract shape the screen

A dashboard needs predictable pagination, stable identifiers, and error responses it can translate into useful feedback. Display only actions the user can actually perform, while enforcing authorization on the server. Hiding a control is not an access check.

Good frontend work makes uncertainty understandable.

Review a slow connection, an empty response, a failed refresh, and two rapid filter changes. Those scenarios reveal more about the experience than another screenshot of the populated table.

Further reading

Vue: accessibility guide