Skip to Content
DocsReact HooksReal-Time Infinite Queries

Real-Time Infinite Queries

For scenarios where you need paginated, infinite-scrolling data combined with real-time live updates, Actyx RPC provides two combination hooks: useWSInfiniteQuery (WebSocket) and useSSEInfiniteQuery (Server-Sent Events).

These hooks attach a real-time transport stream on top of the useInfiniteQuery pagination engine. Incoming live events are automatically appended to the cached page data and can also trigger custom cache mutations (prepend, update, remove) before the data reaches your component.


useWSInfiniteQuery

Combines useInfiniteQuery with useWS for bi-directional WebSocket-powered infinite lists.

import { useWSInfiniteQuery } from "@explita/actyx-rpc-react"; import { getFeedPosts } from "@/backend/procedures"; function LiveFeed() { const { data: posts, fetchNext, hasNext, isFetching, isConnected, error, } = useWSInfiniteQuery(getFeedPosts, { // Infinite query options queryOpts: { input: { limit: 10 }, getNextPageParam: (lastPage) => lastPage.nextCursor, queryKey: ["feed"], }, // WebSocket options url: "/api/ws/feed", // Called on each incoming WS message onData({ data: newPost, prepend }) { // Prepend incoming live posts to the cache prepend(newPost); }, }); return ( <div> <p>Status: {isConnected ? "🟢 Live" : "🔴 Disconnected"}</p> <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> {hasNext && ( <button onClick={() => fetchNext()} disabled={isFetching}> {isFetching ? "Loading..." : "Load More"} </button> )} </div> ); }

Configuration

useWSInfiniteQuery accepts a combined options object with two groups:

Infinite Query Options (queryOpts):

OptionTypeDefaultDescription
inputWithoutCursor<TInput>—Base input parameters for the first page fetch.
initialPageParamstring | numberundefinedCursor for the first page.
getNextPageParam(lastPage, allPages) => cursor—Determines the next cursor.
queryKeyunknown[]—Cache identification key.
maxPagesnumber—Maximum pages to keep in cache.
enabledbooleantrueEnable/disable fetching on mount.
staleTimenumber | string0Time before data is considered stale.
gcTimenumber | string5minTime before unused cache is garbage collected.
refetchIntervalnumber0Polling interval in ms.
refetchOnWindowFocusbooleanfalseRefetch on window focus.
refetchOnReconnectboolean | "always"trueRefetch on network restore.
keepPreviousDatabooleantrueKeep old data visible during refetch instead of flashing an empty state.
initialDatadata | (() => data)—Pre-populate cache on mount.

WebSocket Options (all useWS options except onData):

OptionTypeDefaultDescription
urlstring—WebSocket server URL.
initialDataTOutput[] | (() => MaybePromise<TOutput[]>)—Pre-populate the data array.
enabledbooleantrueSet to false to prevent connecting.
onData(opts: WSEventContext) => void—Callback receiving { data, action, allData, append, prepend, insert, update } for custom cache mutations.
onError(err) => void—Connection error callback.
onSubscribed() => void—Connection established callback.
onUnsubscribed(evt: CloseEvent) => void—Connection closed callback. Receives the CloseEvent.
onWindowFocus(opts: InfiniteQueryContext) => void—Callback when window regains focus. Receives { data, pages, pageParams, refetch, reset, prepend, append, insert, update, remove, setPages, snapshot }.
onReconnect(opts: InfiniteQueryContext) => void—Callback when network reconnects. Receives the same extended infinite query context as onWindowFocus.

Returned Properties

Returns all properties from useInfiniteQuery plus the following from useWS:

PropertyTypeDescription
send(data) => voidSend a message over the WebSocket.
unsubscribe() => voidManually close the WebSocket connection.
status"idle" | "connecting" | "connected" | "error"Connection state.

All cache mutation helpers (remove, update, prepend, append, insert, setPages, snapshot) from the underlying infinite query are also available.


useSSEInfiniteQuery

Combines useInfiniteQuery with useSSE for one-way SSE-powered infinite lists.

import { useSSEInfiniteQuery } from "@explita/actyx-rpc-react"; import { getNotifications } from "@/backend/procedures"; function NotificationFeed() { const { data: notifications, fetchNext, hasNext, isConnected, lastData: latestNotification, event: lastEvent, error, } = useSSEInfiniteQuery(getNotifications, { // Infinite query options queryOpts: { input: { limit: 20 }, getNextPageParam: (lastPage) => lastPage.nextCursor, queryKey: ["notifications"], }, // SSE options url: "/api/sse/notifications", maxHistory: 50, // Called on each SSE event onData({ data: notification, prepend, event }) { console.log(`Received event: ${event}`); prepend(notification); }, }); return ( <div> <p>Status: {isConnected ? "🟢 Connected" : "🔴 Disconnected"}</p> {latestNotification && ( <p className="latest">Latest: {latestNotification.title}</p> )} <ul> {notifications.map((n) => ( <li key={n.id}>{n.title}</li> ))} </ul> {hasNext && ( <button onClick={() => fetchNext()} disabled={isFetching}> Load Older </button> )} </div> ); }

Configuration

Infinite Query Options (queryOpts): Same as useWSInfiniteQuery above.

SSE Options (all useSSE options except onData):

OptionTypeDefaultDescription
urlstring—SSE endpoint URL.
paramsRecord<string, string>—Query parameters for the SSE URL.
headersRecord<string, string>—Custom request headers.
enabledbooleantrueToggle connection on/off.
maxHistorynumber—Limit accumulated event history.
onData(opts: WSEventContext & { event?: string }) => void—Callback receiving { data, allData, append, prepend, update, event }.
onError(err) => void—Connection error callback.

Returned Properties

Returns all properties from useInfiniteQuery plus the following from useSSE:

PropertyTypeDescription
lastDataTData | undefinedMost recent SSE event payload.
eventstring | undefinedName of the most recent SSE event.
isConnectedbooleanConnection state.
close() => voidManually close the SSE connection.
clear() => voidClear accumulated data history.

onData Callback

Both hooks accept an onData callback that fires on every incoming event/message. It provides the raw payload along with helper functions to manipulate the infinite query cache directly:

onData({ data, action, allData, append, prepend, update, event }) { // data - The incoming event payload // action - `"added"` or `"updated"` (dedup key matched an existing item) // allData - The current flattened data array from all pages // append - Append item to the last page // prepend - Prepend item to the first page // update - Update a matching item by index or predicate // event - (SSE only) The SSE event name string }

If onData is not provided, the hooks default to calling append(data) automatically on each incoming event/message.


Client Proxy Integration

When using the Client Proxy SDK (createClient), infinite query procedures provide .useSSEInfiniteQuery and .useWSInfiniteQuery directly. Because real-time streaming connects to an active stream rather than the query endpoint, the streaming target is specified via stream (or ws):

import { rpc } from "@/lib/rpc/client"; function LiveTodoList() { const { data: todos, fetchNext, hasNext } = rpc.todos.list.useSSEInfiniteQuery({ // Pass another proxy procedure directly (type-safe) stream: rpc.notif.sse, // Or pass an explicit URL string: // stream: "/api/rpc/notif.sse", queryOpts: { getNextPageParam: (lastPage) => lastPage.nextCursor, }, }); return ( <ul> {todos.map((todo) => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ); }
Last updated on