Bext Query (signals)

Reactive data cache on bext signals — TanStack-shaped API (createQuery, createMutation, invalidateQueries, dehydrate/hydrate) without React. The page prefetches /api/poll server-side, dehydrates the cache into the island marker, and the island hydrates its client cache before the first createQuery call — so first paint shows real data, no loading flash.

Tip
Server-side cache dehydration (client.dehydrate()) is serialised into the island marker and shipped in the initial HTML — no extra request needed on mount. Use staleTime to control when the client re-fetches.

Output



Manual query (staleTime 5s)

status: success · fetching: no · stale: no

now =

Live query (refetchInterval 3s)

tick =

src/components/QueryDemo.tsxTSX
"use signals";
/** @jsxImportSource @bext-stack/framework/signals */

import {
  createQueryClient, createQuery, createMutation,
  type DehydratedState,
} from "@bext-stack/framework/query";

export default function QueryDemo(props: { dehydrate?: DehydratedState }) {
  const client = createQueryClient();
  client.hydrate(props.dehydrate);

  const poll = createQuery<{ now: string }>(client, {
    queryKey: ["poll"],
    queryFn: async ({ signal }) => (await fetch("/api/poll", { signal })).json(),
    staleTime: 5_000,
  });

  return (
    <div>
      <p>status: {poll.status.value} · fetching: {poll.isFetching.value ? "yes" : "no"}</p>
      <p>now = {poll.data.value?.now ?? "—"}</p>
      <button onClick={() => poll.refetch()}>refetch</button>
    </div>
  );
}
src/app/examples/query/page.tsxTSX
import { signalsIsland } from "@bext-stack/framework/signals";
import { createQueryClient } from "@bext-stack/framework/query";
import QueryDemo from "../../../components/QueryDemo";

export default async function Page() {
  const client = createQueryClient();
  // Server-side prefetch — populates the cache before the island renders.
  await client.prefetchQuery({
    queryKey: ["poll"],
    queryFn: async () =>
      (await fetch("https://demo.bext.dev/api/poll")).json(),
  });
  return signalsIsland("QueryDemo", QueryDemo, {
    dehydrate: client.dehydrate(),
  });
}