React Hooks
The @semilayer/react package is a thin wrapper around @semilayer/client
that exposes React hooks for search, similarity, direct queries, streaming, and live tail.
Installation
Requires React 19+. The hooks rely on transitional concurrent-mode features and do not polyfill them for older versions.
Provider
Wrap your tree once with <SemiLayerProvider>. It constructs a shared BeamClient and
makes it available to every hook below it.
For SSR or tests, you can pass a pre-built client directly:
Use a public key (pk_live_...) in the browser. Service keys (sk_live_...)
bypass access rules and must never leave the server.
One-shot Hooks
These mirror the three HTTP methods on BeamClient. They run once on mount (and
again when the params change), resolve a Promise, and expose
{ data, loading, error, refetch }.
useSearch
Passing null (or omitting params.query) keeps the hook idle — useful for
deferring the request until the user types.
useSimilar
useQuery
params serialises via JSON.stringify for dependency tracking, so the hook
refetches whenever the shape changes.
Including related rows
All four hooks above — plus useStreamSearch / useStreamQuery below —
accept an optional include field that asks SemiLayer to stitch declared
relations onto each parent row. The planner batches a cross-source fetch
per relation (Postgres + MySQL + Mongo, whatever you configured) and the
hook returns parent rows with the relations already attached.
Failures on individual relations land in meta.includeErrors; the relation
value on each row is [] (hasMany) or null (belongsTo) — you can render
"reviews unavailable" placeholders without the parent list disappearing.
Full reference at Joins → Include Syntax.
Streaming Hooks
These open a WebSocket via BeamClient and progressively populate local state
as rows / events arrive. The socket is shared across all streaming hooks
mounted under the same provider.
useStreamSearch
Progressive-render a search result set.
useStreamQuery
Progressive-render a direct query.
useSubscribe
Live tail — append every insert / update / delete event that matches the
lens (and optional filter).
The events array is capped at maxEvents (default 500) to keep memory bounded.
useObserve
Observe a single record. The first emitted value is the current state; subsequent emissions fire when the record changes.
Cleanup & Cancellation
Every hook cancels in-flight work on unmount or param change:
- One-shot hooks guard state updates behind a
cancelledflag so late responses are dropped after unmount. - Streaming hooks call
iterator.return()on cleanup, which closes the underlying WebSocket op cleanly — no zombie streams.
End-User Identity (RBAC)
For per-user access rules, build a client with withUser() and pass it to the
provider. Doing this per render is cheap — withUser clones the client without
opening a new socket.
useFeed
The magical surface for the feed facet. Takes a codegen-emitted feed
handle directly (not via the provider context). Owns the lifecycle: first-page
fetch on mount, refetch on context change, optional live tick subscription,
context evolution via the customer-named evolve() action.
Options
| Option | Type | Description |
|---|---|---|
context | FeedContext | Caller-controlled context. Hook re-fetches when its stable JSON shape changes. |
pageSize | number | Override the configured pageSize. |
liveUpdates | boolean | Open feed.subscribe on mount; accumulate newCount into liveCount. Default false. |
onEvolve | (delta) => void | Promise<void> | Called when evolve() fires. Use to persist the interaction to your own API. |
evolveOn | 'change' | 'never' | Auto-refetch behavior on evolve(). Default 'change'. |
cache | FeedCache | null | Per-call cache override. Falls through to BeamClient's feedCache. Pass null to disable. |
onCacheMetric | (event) => void | Forwarded to the cache. Wire into your telemetry. |
Returns
| Field | Type | Description |
|---|---|---|
items | FeedItem<M>[] | Accumulated items across fetchMore calls. |
fetchMore | () => Promise<void> | Load the next page. No-op when cursor === null. |
refetch | () => Promise<void> | Reset state + re-fetch the first page. |
evolve | (delta) => Promise<void> | Apply an interaction. See below. |
liveCount | number | Running count from feed.tick since last refetch. |
evolved | boolean | True when server reports a contextHash change. UI cue. |
isLoading | boolean | Underway: the most recent fetch. |
error | Error | null | Most recent error, if any. |
cursor | string | null | Current cursor. Null when no more pages. |
evolve(delta) — the interaction action
The hook never names a specific interaction. The customer's app supplies
meta ({ kind: 'positive' }, { kind: 'follow' }, { kind: 'save' },
whatever vocabulary the product uses). The customer's onEvolve callback
persists however they want.
The action sequence:
- If
setContext+contextDeltasupplied → apply immediately (next render's effect refetches). - Fire
onEvolve(awaited). - If
evolveOn === 'change'and nosetContextwas supplied → refetch first page manually.
When the user has many signals
Past ~50-100 signals, both the wire and the math degrade. Trim at your data
layer — the customer owns what's important to surface. Three patterns in
Signals: recency window, pre-computed taste vector,
or recordVector mode (no scaling concern at all).
See Also
@semilayer/clientreference — the underlying runtime client these hooks wrap.- HTTP & WebSocket protocol — wire-level details for the streaming endpoints.
- Feed facet overview — config + concepts.
- Auth & RBAC guide — how user tokens feed into lens access rules.