React
Tearing-free useStitch and useStitchStream hooks built on useSyncExternalStore, over the framework-agnostic @stitchapi/query-core store — plus an optional TanStack Query adapter.
Use @stitchapi/react when you call stitches from React components and want the
call's lifecycle — pending / success / error, cancellation, refetch, and
streaming re-renders — managed for you. useStitch is the request/response
hook; useStitchStream re-renders as each delta chunk arrives, which is the
differentiator over plain request/response query libraries.
The hooks are a thin layer over @stitchapi/query-core,
the framework-agnostic store that owns the reactive lifecycle. React reads it
through useSyncExternalStore, so updates are tearing-free.
Example
Install the hooks, the store, core, and your React:
npm install @stitchapi/react @stitchapi/query-core stitchapi reactstitchapi and react are peer dependencies; @tanstack/react-query is an
optional peer, needed only for the queryOptions adapter below.
useStitch — request / response
Declare a stitch once, then drive it from a component (or a custom hook). The
result carries the validated data, the error, the boolean status flags, and
imperative refetch / cancel handles:
import { } from '@stitchapi/react';
import { } from 'stitchapi';
import { } from 'zod';
const = ({
: 'https://api.example.com',
: '/users',
: .(.({ : .(), : .() })),
});
// A custom hook wrapping useStitch — re-renders on the query's transitions.
function (: string) {
const { , , , } = (, {
: { },
});
return { , , , };
}The query is re-created (and re-fetched) when a structural key of input changes;
pass options.deps to control that explicitly. An inline stitch is safe — its
function identity does not trigger a re-create — and the in-flight run is aborted
on unmount.
useStitchStream — live deltas
For a streaming stitch (an sse or stream surface),
useStitchStream re-renders as each chunk arrives. chunks is the running list,
data is the accumulated array (mode: 'append', default) or the latest chunk
(mode: 'replace'), and status is 'streaming' until the terminal result,
then 'success':
import { } from '@stitchapi/react';
import { } from 'stitchapi/sse';
const = ({ : 'https://api.example.com/chat' });
// Re-renders as each `delta` chunk streams in — the streaming differentiator.
function (: string) {
const { , } = (, {
: { },
});
return { , };
}Both hooks return the same shape — data, error, status, chunks, the
isPending / isError / isSuccess / isStreaming flags, plus refetch and
cancel.
The shared store: @stitchapi/query-core
The hooks hold almost no logic. All of it — running the call under an
AbortController, publishing status transitions, folding delta chunks into
state, cancel() by aborting, refetch() by re-running — lives in
@stitchapi/query-core's createStitchQuery, a subscribe / getSnapshot
handle with no framework imports and no node:*, so it is browser- and
edge-safe.
import { } from '@stitchapi/query-core';
import { } from 'stitchapi';
import { } from 'zod';
const = ({
: 'https://api.example.com',
: '/users',
: .(.({ : .(), : .() })),
});
const = (, { : { : 'ada' } });
const = .(() => {
const = .();
if (.) .(.);
});That subscribe / getSnapshot shape is exactly what useSyncExternalStore
(and the equivalent external-store primitives in Vue, Svelte, and
Solid) consume directly. Because the whole reactive lifecycle lives in the
store and not the binding, those bindings are the same few lines against their own
primitive — which is why @stitchapi/react is small and Vue/Svelte/Solid bindings
are cheap follow-ons rather than rewrites.
Optional: TanStack Query
Already on TanStack Query?
queryOptions(stitch, input) returns a plain { queryKey, queryFn } object you
pass straight to useQuery — it imports nothing from @tanstack/react-query, so
it works even if you never install it:
import { } from '@stitchapi/react';
import { } from '@tanstack/react-query';
import { } from 'stitchapi';
import { } from 'zod';
const = ({
: 'https://api.example.com',
: '/users',
: .(.({ : .(), : .() })),
});
function (: string) {
return ((, { : { } }));
}Reach for queryOptions when TanStack Query already owns your view state
and you want a stitch as the queryFn; reach for useStitch /
useStitchStream when you want StitchAPI to own the lifecycle directly —
especially for streaming, which useQuery does not model. See the TanStack
Query guide for how the two layers
split.
See also
Hono
Edge-ready Hono middleware for StitchAPI — a principal-bound seam on c.var.stitch, an SSE bridge with streamStitchSse, and stitch errors mapped to HTTPException.
TanStack Query
A stitch is the queryFn. StitchAPI owns the call’s resilience; TanStack Query (or SWR) owns view state — they compose, they do not compete.