Skip to main content
query() creates a reactive data-fetching primitive. Pass it a SDK method call and it returns reactive properties (data, loading, error) that auto-update in the DOM. It supports SSR data pre-fetching and integrates with the entity store for cross-component cache invalidation.

query()

Pass a generated SDK method call. The cache key and types are inferred automatically from the SDK:
import { query } from '@vertz/ui/query';

function TaskList() {
  const tasks = query(api.tasks.list());

  return (
    <div>
      {tasks.loading && <p>Loading...</p>}
      {tasks.error && <p>Error: {tasks.error.message}</p>}
      {tasks.data?.items.map((task) => (
        <div key={task.id}>{task.title}</div>
      ))}
    </div>
  );
}
SDK methods return a QueryDescriptor — an object that carries the fetch function, a deterministic cache key, and entity metadata. query() unwraps the Result for you: if the SDK call succeeds, data gets the value; if it fails, error gets the error. See the Generated SDK reference for details on how SDK methods and the Result type work.

Signature

function query<T, E>(
  descriptor: QueryDescriptor<T, E>,
  options?: Omit<QueryOptions<T>, 'key'>,
): QueryResult<T, E>;

QueryResult

The returned object has reactive properties that auto-update in the DOM — the compiler handles reactivity for you.
PropertyTypeDescription
dataT | undefinedResolved data, or undefined while loading
loadingbooleantrue during the initial fetch
revalidatingbooleantrue during background refetch (stale-while-revalidate)
errorE | undefinedError from the last failed fetch
idlebooleantrue when the query has never fetched (e.g. thunk returned null)
refetch()() => voidRe-execute the query (shows loading state)
revalidate()() => voidBackground refetch (keeps current data visible)
dispose()() => voidCancel the query and clean up subscriptions
refetch() resets data to undefined and shows loading state. revalidate() keeps the current data visible while fetching fresh data in the background (stale-while-revalidate pattern).

QueryOptions

OptionTypeDefaultDescription
keystringCache key (auto-inferred from SDK descriptors)
initialDataTInitial data to use before the first fetch completes
debouncenumberDebounce interval in ms before executing
cacheCacheStore<T>Custom cache store
ssrTimeoutnumberMax time to wait for the query during SSR (ms)
refetchIntervalnumber | false | (data, iteration) => number | falsefalsePolling interval in ms, or a function for dynamic intervals

invalidate()

Force all queries with a matching key to refetch.
import { invalidate } from '@vertz/ui/query';

// After a mutation succeeds, invalidate the list
const result = await api.tasks.create({ title: 'New task' });
if (result.ok) {
  invalidate('task-list');
}

Signature

function invalidate(key: string): void;

SSR support

Queries execute during SSR automatically. The server waits for queries to resolve (up to ssrTimeout), serializes the data, and the client hydrates without re-fetching.
const tasks = query(api.tasks.list(), { ssrTimeout: 500 });
// Server: waits up to 500ms for data, injects into HTML
// Client: hydrates with server data, no loading flash

Types

interface QueryOptions<T> {
  initialData?: T;
  debounce?: number;
  key?: string;
  cache?: CacheStore<T>;
  ssrTimeout?: number;
  refetchInterval?: number | false | ((data: T | undefined, iteration: number) => number | false);
}

interface QueryResult<T, E = unknown> {
  readonly data: T | undefined;
  readonly loading: boolean;
  readonly revalidating: boolean;
  readonly error: E | undefined;
  readonly idle: boolean;
  refetch: () => void;
  revalidate: () => void;
  dispose: () => void;
}

interface CacheStore<T> {
  get(key: string): T | undefined;
  set(key: string, value: T): void;
  has(key: string): boolean;
  clear(): void;
}