useRealtime
The useRealtime hook subscribes to realtime messages from Inngest functions in React components. It manages the WebSocket connection, reconnection, buffering, and provides typed access to messages by topic.
import { useRealtime } from "inngest/react";
import { pipelineChannel } from "../inngest/channels";
function Pipeline({ runId }: { runId: string }) {
const ch = pipelineChannel({ runId });
const { status, latest, history, runStatus, result } = useRealtime({
channel: ch,
topics: ["status", "tokens"],
token: () => fetch("/api/realtime-token").then((r) => r.text()),
});
return (
<div>
<p>Connection: {status} | Run: {runStatus}</p>
{latest.status && <p>{latest.status.data.message}</p>}
</div>
);
}
useRealtime(options)
- Name
channel- Type
- ChannelInstance | string
- Required
- optional
- Description
The channel to subscribe to. Can be a channel instance from
realtime.channel()or a plain string. When using a channel instance, topic data is automatically typed.
- Name
topics- Type
- string[]
- Required
- optional
- Description
The topics to subscribe to within the channel. Only messages for these topics will be delivered.
- Name
token- Type
- string | Token | () => Promise<string | Token>
- Required
- optional
- Description
Authentication token for the subscription. Can be a pre-minted token string, a
Tokenobject fromgetSubscriptionToken, or an async factory function that fetches a token. The factory pattern is recommended as it's called on mount and on reconnect.
- Name
enabled- Type
- boolean
- Required
- optional
- Description
Whether the subscription is active. Set to
falseto pause without unmounting. Defaults totrue.
- Name
validate- Type
- boolean
- Required
- optional
- Description
Enable subscriber-side schema validation on incoming messages. Defaults to
true.
- Name
historyLimit- Type
- number | null
- Required
- optional
- Description
Maximum number of messages to retain in
historyanddata. Set tonullfor unbounded history. Defaults to100.
- Name
bufferInterval- Type
- number
- Required
- optional
- Description
Milliseconds to buffer incoming messages before triggering a re-render. Useful for high-frequency streams to reduce render pressure. Defaults to
0(immediate).
- Name
reconnect- Type
- boolean
- Required
- optional
- Description
Automatically reconnect on disconnect. Defaults to
true.
- Name
reconnectMinMs- Type
- number
- Required
- optional
- Description
Minimum delay between reconnect attempts in milliseconds. Defaults to
250.
- Name
reconnectMaxMs- Type
- number
- Required
- optional
- Description
Maximum delay between reconnect attempts in milliseconds (exponential backoff cap). Defaults to
5000.
- Name
pauseOnHidden- Type
- boolean
- Required
- optional
- Description
Pause the subscription when the browser tab is hidden, resuming when it becomes visible. Defaults to
true.
- Name
autoCloseOnTerminal- Type
- boolean
- Required
- optional
- Description
Automatically close the subscription when the function run reaches a terminal status (
completed,failed, orcancelled). Defaults totrue.
Return value
- Name
status- Type
- "idle" | "connecting" | "open" | "closed" | "error"
- Required
- optional
- Description
The WebSocket connection status.
- Name
runStatus- Type
- "unknown" | "running" | "completed" | "failed" | "cancelled"
- Required
- optional
- Description
The lifecycle status of the Inngest function run. Updated from run-level messages on the channel.
- Name
latest- Type
- Record<string, Message | undefined>
- Required
- optional
- Description
A map of the most recent message for each topic. Access typed data via
latest.topicName?.data. Only includes topics specified inoptions.topics.
- Name
history- Type
- Message[]
- Required
- optional
- Description
All received messages in chronological order, bounded by
historyLimit.
- Name
error- Type
- Error | null
- Required
- optional
- Description
The most recent connection error, or
nullif connected successfully.
- Name
result- Type
- unknown
- Required
- optional
- Description
The function's return value, extracted from the terminal run message when the function completes.
- Name
reset- Type
- () => void
- Required
- optional
- Description
Clears all accumulated state (
history,latest,error,result) and resets to initial values.
- Name
data- Type
- Message[]
- Required
- optional
- Description
Alias for
history. All received messages bounded byhistoryLimit.
- Name
latestData- Type
- Message | null
- Required
- optional
- Description
The single most recent message across all topics.
- Name
freshData- Type
- Message[]
- Required
- optional
- Description
Messages received since the last render cycle. Useful for animations or processing only new items.
Connection status
The status field tracks the WebSocket connection lifecycle:
| Status | Description |
|---|---|
idle | Hook is mounted but hasn't started connecting (e.g., enabled: false) |
connecting | Establishing the WebSocket connection |
open | Connected and receiving messages |
closed | Connection closed (manually or by autoCloseOnTerminal) |
error | Connection failed. Check error for details |
Run status
The runStatus field tracks the Inngest function's execution lifecycle:
| Status | Description |
|---|---|
unknown | No run status received yet |
running | Function is actively executing |
completed | Function finished successfully. result is available |
failed | Function failed after exhausting retries |
cancelled | Function was cancelled |
When autoCloseOnTerminal is true (the default), the subscription closes automatically once runStatus reaches completed, failed, or cancelled.
Token factory pattern
The recommended approach is to pass an async factory function for token. This function is called when the hook mounts and on each reconnect, ensuring fresh tokens.
// app/actions.ts
"use server";
import { getSubscriptionToken } from "inngest/react";
import { inngest } from "../inngest/client";
import { pipelineChannel } from "../inngest/channels";
export async function getToken(runId: string) {
const ch = pipelineChannel({ runId });
const token = await getSubscriptionToken(inngest, {
channel: ch,
topics: ["status", "tokens"],
});
return token.key;
}
// app/page.tsx
"use client";
import { useRealtime } from "inngest/react";
import { pipelineChannel } from "../inngest/channels";
import { getToken } from "./actions";
function Pipeline({ runId }: { runId: string }) {
const { status, latest } = useRealtime({
channel: pipelineChannel({ runId }),
topics: ["status", "tokens"],
token: () => getToken(runId),
});
return <p>{latest.status?.data.message}</p>;
}
Typed topic access
When you pass a channel instance (not a plain string), latest is typed per-topic:
const ch = pipelineChannel({ runId });
const { latest } = useRealtime({
channel: ch,
topics: ["status", "tokens"],
token: () => getToken(runId),
});
// Typed access: TypeScript knows the shape of each topic's data
latest.status?.data.message; // string
latest.tokens?.data.token; // string
History management
By default, history retains the last 100 messages. Adjust with historyLimit:
// Keep last 500 messages
useRealtime({ ..., historyLimit: 500 });
// Keep all messages (unbounded, use with caution)
useRealtime({ ..., historyLimit: null });
// Keep only 10 messages
useRealtime({ ..., historyLimit: 10 });
Buffering
For high-frequency streams (like token-by-token AI output), use bufferInterval to batch re-renders:
const { history } = useRealtime({
channel: ch,
topics: ["tokens"],
token: () => getToken(runId),
bufferInterval: 100, // Batch messages, re-render at most every 100ms
});
Conditional subscription
Use enabled to start or stop the subscription without unmounting the component:
const [runId, setRunId] = useState<string | null>(null);
const { status, latest } = useRealtime({
channel: pipelineChannel({ runId: runId ?? "" }),
topics: ["status"],
token: () => getToken(runId!),
enabled: runId !== null, // Only connect when we have a runId
});