React hooks
Render live, typed updates in React with the useRealtime hook.
The useRealtime hook from inngest/react connects a component to a Realtime channel. It fetches tokens, reconnects after drops, pauses in hidden tabs, and returns each topic's latest message with its type. You write the UI; the hook handles the connection.
Connect a component
Pass the hook three things:
- A channel instance from your shared channel definition.
- The topics to receive, as a literal array with
as const. - A token factory that calls your server. See Subscription tokens.
app/job-progress.tsx"use client";
import { useRealtime } from "inngest/react";
import { jobChannel } from "@/inngest/channels";
import { fetchJobToken } from "./actions";
export function JobProgress({ jobId }: { jobId: string }) {
const { connectionStatus, runStatus, messages, error, reset } = useRealtime({
channel: jobChannel({ jobId }),
topics: ["status", "logs"] as const,
token: () => fetchJobToken(jobId),
});
return (
<div>
<p>Connection: {connectionStatus}</p>
<p>Run: {runStatus}</p>
<p>Status: {messages.byTopic.status?.data.message ?? "Waiting"}</p>
<p>Log lines received: {messages.all.length}</p>
{error && <p>{error.message}</p>}
<button onClick={() => reset()}>Clear</button>
</div>
);
}
What the hook returns
connectionStatus:idle,connecting,open,paused,closed, orerror.runStatus: the publishing run's state:unknown,running,completed,failed, orcancelled.messages.byTopic: the latest message for each topic, typed by that topic's schema.messages.all: retained messages in order, up tohistoryLimit.messages.lastandmessages.delta: the newest message and the batch delivered in the latest update.result: the run's return value once it completes, when available.error,isPaused, andpauseReason: the reason a connection stopped.reset(): clears retained messages, result, and error.
Choose options
| Option | Default | Use it to |
|---|---|---|
enabled | true | Wait to connect until you have an ID, such as enabled: !!jobId. |
bufferInterval | 0 | Batch renders every N milliseconds for fast streams. |
historyLimit | 100 | Cap messages.all. Set null to keep every message. |
validate | true | Validate incoming messages against topic schemas. |
pauseOnHidden | true | Pause while the browser tab is hidden. |
autoCloseOnTerminal | true | Close the connection when the run completes, fails, or is cancelled. |
reconnect | true | Reconnect automatically, backing off from reconnectMinMs (250) to reconnectMaxMs (5000). |
key | none | Change it to reset state and reconnect, for example when a job ID changes. |
The useRealtime reference lists every option and return type.
Work with typed messages
Pass a channel instance and a literal topics array, and TypeScript narrows each payload by topic:
const { messages } = useRealtime({
channel,
topics: ["status", "artifact"] as const,
token: () => fetchJobToken(jobId),
});
messages.byTopic.status?.data.message;
messages.byTopic.artifact?.data.title;
messages.all and messages.delta also include run lifecycle messages, which have kind: "run" and an untyped payload. Skip them before you check topic:
for (const message of messages.delta) {
if (message.kind === "run") continue;
if (message.topic === "status") {
console.log(message.data.message);
}
}
Next steps
- Stream AI responses renders a token stream with
bufferIntervalandhistoryLimit. - Troubleshooting explains paused, closed, and error states.
- v3 hook docs cover the archived
useInngestSubscription()hook.