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(),
    });
}