Skip to content

Loading States

Handling asynchronous data fetching with appropriate loading indicators.

Problem

You need to fetch data asynchronously and show appropriate feedback while loading.

Solution

Track loading state explicitly and render skeletons, spinners, or error messages.

Complete Example

This example simulates async work with setTimeout so it’s runnable without network access:

import { createApp, ui, rgb } from "@rezi-ui/core";
import { createNodeBackend } from "@rezi-ui/node";

type LoadingState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: string };

type User = { id: string; name: string };

type State = {
  users: LoadingState<User[]>;
};

const app = createApp<State>({
  backend: createNodeBackend(),
  initialState: { users: { status: "idle" } },
});

function fetchUsers(): void {
  app.update((s) => ({ ...s, users: { status: "loading" } }));

  setTimeout(() => {
    const ok = Math.random() > 0.2;
    if (!ok) {
      app.update((s) => ({ ...s, users: { status: "error", error: "Failed to load users" } }));
      return;
    }
    app.update((s) => ({
      ...s,
      users: {
        status: "success",
        data: [
          { id: "1", name: "Ada" },
          { id: "2", name: "Linus" },
          { id: "3", name: "Grace" },
        ],
      },
    }));
  }, 600);
}

function renderUsers(state: State) {
  switch (state.users.status) {
    case "idle":
      return ui.button({ id: "load", label: "Load Users", onPress: fetchUsers });
    case "loading":
      return ui.column({ gap: 1 }, [
        ui.spinner({ label: "Loading…" }),
        ui.skeleton(15),
        ui.skeleton(15),
        ui.skeleton(15),
      ]);
    case "error":
      return ui.empty("Error", {
        description: state.users.error,
        action: ui.button({ id: "retry", label: "Retry", onPress: fetchUsers }),
      });
    case "success":
      return ui.column({ gap: 1 },
        state.users.data.map((user) => ui.text(user.name, { key: user.id }))
      );
  }
}

app.view((state) =>
  ui.column({ flex: 1, gap: 1, p: 1 }, [
    ui.text("Users", { style: { bold: true } }),
    ui.divider(),
    renderUsers(state),
  ])
);

app.keys({ q: () => app.stop(), "ctrl+c": () => app.stop() });

await app.start();

Explanation

  • Model async work explicitly with a tagged union (idle/loading/success/error).
  • Render a skeleton/spinner while loading.
  • Use an empty/error state with a retry action on failure.
  • Skeleton - Loading placeholders
  • Spinner - Animated loading indicator
  • Empty - Empty and error states