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:

  1. A channel instance from your shared channel definition.
  2. The topics to receive, as a literal array with as const.
  3. 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, or error.
  • runStatus: the publishing run's state: unknown, running, completed, failed, or cancelled.
  • messages.byTopic: the latest message for each topic, typed by that topic's schema.
  • messages.all: retained messages in order, up to historyLimit.
  • messages.last and messages.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, and pauseReason: the reason a connection stopped.
  • reset(): clears retained messages, result, and error.

Choose options

OptionDefaultUse it to
enabledtrueWait to connect until you have an ID, such as enabled: !!jobId.
bufferInterval0Batch renders every N milliseconds for fast streams.
historyLimit100Cap messages.all. Set null to keep every message.
validatetrueValidate incoming messages against topic schemas.
pauseOnHiddentruePause while the browser tab is hidden.
autoCloseOnTerminaltrueClose the connection when the run completes, fails, or is cancelled.
reconnecttrueReconnect automatically, backing off from reconnectMinMs (250) to reconnectMaxMs (5000).
keynoneChange 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