Skip to main content

Data Hooks

useAsync

Handle async operations with loading/error state.
import { defineWidget, useAsync, ui } from "@rezi-ui/core";

const UserProfile = defineWidget<{ userId: string }>((props, ctx) => {
  const user = useAsync(ctx, async () => {
    const res = await fetch(`/api/users/${props.userId}`);
    return res.json();
  }, [props.userId]);

  if (user.loading) return ui.text("Loading...");
  if (user.error) return ui.text("Error loading user");
  return ui.text(user.data?.name ?? "Unknown");
});

useInterval

Run callback at regular intervals.
import { defineWidget, useInterval, ui } from "@rezi-ui/core";

const Clock = defineWidget((props, ctx) => {
  const [time, setTime] = ctx.useState(() => new Date().toLocaleTimeString());

  useInterval(ctx, () => {
    setTime(new Date().toLocaleTimeString());
  }, 1000);

  return ui.text(time);
});

useStream

Consume async iterables/streams.
import { defineWidget, useStream, ui } from "@rezi-ui/core";

const StreamViewer = defineWidget<{ stream: AsyncIterable<string> }>((props, ctx) => {
  const state = useStream(ctx, props.stream, [props.stream]);

  if (state.loading) return ui.text("Connecting...");
  if (state.error) return ui.text("Stream error");
  return ui.text(state.value ?? "Waiting for data...");
});

useWebSocket

Connect to WebSocket with auto-reconnect.
import { defineWidget, useWebSocket, ui } from "@rezi-ui/core";

const LiveFeed = defineWidget<{ url: string }>((props, ctx) => {
  const ws = useWebSocket(ctx, props.url, {
    reconnectMs: 3000,
    onMessage: (data) => console.log("Received:", data),
  });

  return ui.column([
    ui.text(`Status: ${ws.state}`),
    ui.text(`Messages: ${ws.messages.length}`),
    ...ws.messages.slice(-10).map((msg) => ui.text(msg)),
  ]);
});

useEventSource

Connect to Server-Sent Events.
import { defineWidget, useEventSource, ui } from "@rezi-ui/core";

const SSEViewer = defineWidget<{ url: string }>((props, ctx) => {
  const sse = useEventSource(ctx, props.url, {
    eventType: "message",
    reconnectMs: 5000,
  });

  return ui.column([
    ui.text(`Connected: ${sse.connected}`),
    ui.text(sse.lastMessage?.data ?? "No messages"),
  ]);
});

useTail

Follow file tail (requires tail source factory).
import { createNodeApp, createNodeTailSource } from "@rezi-ui/node";
import { defineWidget, useTail, ui } from "@rezi-ui/core";

const LogTail = defineWidget<{ path: string }>((props, ctx) => {
  const logs = useTail(ctx, {
    source: createNodeTailSource(props.path),
    maxLines: 100,
  }, [props.path]);

  return ui.column([
    ui.text(`Lines: ${logs.lines.length}`),
    ...logs.lines.map((line) => ui.text(line)),
  ]);
});

Utility Hooks

useDebounce

Debounce a value with delay.
import { defineWidget, useDebounce, ui } from "@rezi-ui/core";

const Search = defineWidget((props, ctx) => {
  const [query, setQuery] = ctx.useState("");
  const debouncedQuery = useDebounce(ctx, query, 500);

  ctx.useEffect(() => {
    if (debouncedQuery) {
      // Search API called only after 500ms of no changes
      searchAPI(debouncedQuery);
    }
  }, [debouncedQuery]);

  return ui.column([
    ui.input({ id: ctx.id("search"), value: query, onChange: setQuery }),
    ui.text(`Searching for: ${debouncedQuery}`),
  ]);
});

usePrevious

Track previous render’s value.
import { defineWidget, usePrevious, ui } from "@rezi-ui/core";

const ChangeDetector = defineWidget<{ value: number }>((props, ctx) => {
  const prevValue = usePrevious(ctx, props.value);
  const changed = prevValue !== undefined && prevValue !== props.value;

  return ui.column([
    ui.text(`Current: ${props.value}`),
    ui.text(`Previous: ${prevValue ?? "N/A"}`),
    ui.text(changed ? "Value changed!" : "No change"),
  ]);
});

Type Safety

import type {
  UseAsyncState,
  UseStreamState,
  UseWebSocketState,
  UseEventSourceState,
  UseTailState,
} from "@rezi-ui/core";

State Hooks

useState and useRef

Lifecycle

useEffect

Build docs developers (and LLMs) love