Skip to content

Loading and Error States

Async UIs need deliberate loading, error, empty, and success states. Users should never stare at a blank screen wondering if anything happened. This lesson standardizes async UI patterns.

The Four Async UI States

Every data-driven view should handle: loading (in progress), error (request failed), empty (success but no data), and success (render content). Explicit branching prevents ambiguous UI.

TanStack Query exposes isLoading, isError, error, and isFetching — map them directly to UI components.

function UserList() {
  const { data, isLoading, isError, error, refetch } = useUsers();

  if (isLoading) return <Skeleton rows={5} />;
  if (isError) return <ErrorPanel message={error.message} onRetry={refetch} />;
  if (data.length === 0) return <EmptyState title="No users | PHPKINGDOM" />;
  return <Table rows={data} />;
}

Early returns keep each state's UI isolated and testable.

State Mapping

isLoading     → skeleton / spinner (no data yet)
isFetching    → subtle refresh indicator (has stale data)
isError       → error message + retry
isSuccess     → content (check empty)
  • Distinguish initial load from background refetch.
  • Offer retry actions on recoverable errors.
  • Empty state is not an error — message should guide next action.
  • Use ARIA live regions for dynamic error announcements.

Async UI State Guide

Map query status to UI components.

Status UI Pattern
Initial loading Skeleton or centered spinner
Background fetch Small inline indicator
Error Alert + retry button
Empty Illustration + CTA
Success Data view
Optimistic Show expected result, rollback on fail

Skeletons vs Spinners

Skeletons preserve layout and feel faster for content-heavy pages. Spinners suit indeterminate short waits. Avoid full-page spinners for partial data loads.

Error Boundaries vs Error State

Error boundaries catch render errors; async fetch errors are expected and handled with error state UI — not error boundaries.

Common Mistakes

  • Showing nothing during loading.
  • Generic 'Something went wrong' without retry.
  • Treating empty results as errors.
  • Full-page spinner for small widget refetch.

Key Takeaways

  • Model loading, error, empty, and success explicitly.
  • Map TanStack Query flags to dedicated UI components.
  • Provide retry for transient failures.
  • Use skeletons for better perceived performance.

Pro Tip

Create <AsyncBoundary> or <QueryStatus> wrapper components reused across pages for consistent UX.