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: { initialInput: { 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
initialInputWithoutCursor<TInput>Base input parameters for the first page fetch.
initialPageParamstring | numberundefinedCursor for the first page.
getNextPageParam(lastPage, allPages) => cursorDetermines the next cursor.
queryKeyunknown[]Cache identification key.
maxPagesnumberMaximum 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
urlstringWebSocket server URL.
initialDataTOutput[] | (() => MaybePromise<TOutput[]>)Pre-populate the data array.
enabledbooleantrueSet to false to prevent connecting.
onData(opts: WSEventContext) => voidCallback receiving { data, action, allData, append, prepend, update } for custom cache mutations.
onError(err) => voidConnection error callback.
onSubscribed() => voidConnection established callback.
onUnsubscribed() => voidConnection closed callback.
onWindowFocus(opts: { data: TData[]; refetch: () => Promise<void> }) => voidCallback when window regains focus. Receives infinite query data and a refetch function.
onReconnect(opts: { data: TData[]; refetch: () => Promise<void> }) => voidCallback when network reconnects. Receives infinite query data and a refetch function.

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: { initialInput: { 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
urlstringSSE endpoint URL.
paramsRecord<string, string>Query parameters for the SSE URL.
headersRecord<string, string>Custom request headers.
enabledbooleantrueToggle connection on/off.
maxHistorynumberLimit accumulated event history.
onData(opts: WSEventContext & { event?: string }) => voidCallback receiving { data, allData, append, prepend, update, event }.
onError(err) => voidConnection 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.

Last updated on