--- title: "Run updates in React" sidebarTitle: "Run updates" description: "Build progress bars, status indicators, and live dashboards by subscribing to background task updates from React components." --- **Subscribe to a run and your component re-renders whenever its status, metadata, or tags change.** Build progress bars, deployment monitors, or any UI that needs to reflect what a background task is doing right now. For streaming continuous data (AI tokens, file chunks), see [Streaming](/realtime/react-hooks/streams) instead. ## Trigger + subscribe combo hooks Trigger a task and immediately subscribe to its run. Details in the [triggering](/realtime/react-hooks/triggering) section. - **[`useRealtimeTaskTrigger`](/realtime/react-hooks/triggering#userealtimetasktrigger)** - Trigger a task and subscribe to the run - **[`useRealtimeTaskTriggerWithStreams`](/realtime/react-hooks/triggering#userealtimetasktriggerwithstreams)** - Trigger a task and subscribe to both run updates and streams ## Subscribe hooks ### useRealtimeRun The `useRealtimeRun` hook allows you to subscribe to a run by its ID. ```tsx "use client"; // This is needed for Next.js App Router or other RSC frameworks import { useRealtimeRun } from "@trigger.dev/react-hooks"; export function MyComponent({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken, }); if (error) return
Error: {error.message}
; return
Run: {run.id}
; } ``` To correctly type the run's payload and output, you can provide the type of your task to the `useRealtimeRun` hook: ```tsx import { useRealtimeRun } from "@trigger.dev/react-hooks"; import type { myTask } from "@/trigger/myTask"; export function MyComponent({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken, }); if (error) return
Error: {error.message}
; // Now run.payload and run.output are correctly typed return
Run: {run.id}
; } ``` You can supply an `onComplete` callback to the `useRealtimeRun` hook to be called when the run is completed or errored. This is useful if you want to perform some action when the run is completed, like navigating to a different page or showing a notification. ```tsx import { useRealtimeRun } from "@trigger.dev/react-hooks"; export function MyComponent({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken, onComplete: (run, error) => { console.log("Run completed", run); }, }); if (error) return
Error: {error.message}
; return
Run: {run.id}
; } ``` When you only need run status (for example, a progress bar or completion badge), you can omit large fields like `payload` and `output` by passing `skipColumns`. This reduces the data sent over the wire and avoids issues such as "Large HTTP Payload" warnings in tools like Sentry. ```tsx import { useRealtimeRun } from "@trigger.dev/react-hooks"; export function RunStatusBadge({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken, skipColumns: ["payload", "output"], }); if (error) return Error; if (!run) return Loading…; return Status: {run.status}; } ``` You can skip any of: `payload`, `output`, `metadata`, `startedAt`, `delayUntil`, `queuedAt`, `expiredAt`, `completedAt`, `number`, `isTest`, `usageDurationMs`, `costInCents`, `baseCostInCents`, `ttl`, `payloadType`, `outputType`, `runTags`, `error`. The `useRealtimeRunsWithTag` hook also accepts a `skipColumns` option in the same way. See our [run object reference](/realtime/run-object) for the complete schema and [How it Works documentation](/realtime/how-it-works) for more technical details. ### useRealtimeRunsWithTag The `useRealtimeRunsWithTag` hook allows you to subscribe to multiple runs with a specific tag. ```tsx "use client"; // This is needed for Next.js App Router or other RSC frameworks import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks"; export function MyComponent({ tag }: { tag: string }) { const { runs, error } = useRealtimeRunsWithTag(tag); if (error) return
Error: {error.message}
; return (
{runs.map((run) => (
Run: {run.id}
))}
); } ``` To correctly type the runs payload and output, you can provide the type of your task to the `useRealtimeRunsWithTag` hook: ```tsx import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks"; import type { myTask } from "@/trigger/myTask"; export function MyComponent({ tag }: { tag: string }) { const { runs, error } = useRealtimeRunsWithTag(tag); if (error) return
Error: {error.message}
; // Now runs[i].payload and runs[i].output are correctly typed return (
{runs.map((run) => (
Run: {run.id}
))}
); } ``` If `useRealtimeRunsWithTag` could return multiple different types of tasks, you can pass a union of all the task types to the hook: ```tsx import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks"; import type { myTask1, myTask2 } from "@/trigger/myTasks"; export function MyComponent({ tag }: { tag: string }) { const { runs, error } = useRealtimeRunsWithTag(tag); if (error) return
Error: {error.message}
; // You can narrow down the type of the run based on the taskIdentifier for (const run of runs) { if (run.taskIdentifier === "my-task-1") { // run is correctly typed as myTask1 } else if (run.taskIdentifier === "my-task-2") { // run is correctly typed as myTask2 } } return (
{runs.map((run) => (
Run: {run.id}
))}
); } ``` ### useRealtimeBatch The `useRealtimeBatch` hook allows you to subscribe to a batch of runs by its the batch ID. ```tsx "use client"; // This is needed for Next.js App Router or other RSC frameworks import { useRealtimeBatch } from "@trigger.dev/react-hooks"; export function MyComponent({ batchId }: { batchId: string }) { const { runs, error } = useRealtimeBatch(batchId); if (error) return
Error: {error.message}
; return (
{runs.map((run) => (
Run: {run.id}
))}
); } ``` See our [Realtime documentation](/realtime) for more information. ## Using metadata to show progress in your UI All realtime hooks automatically include metadata updates. Whenever your task updates metadata using `metadata.set()`, `metadata.append()`, or other metadata methods, your component will re-render with the updated data. To learn how to write tasks using metadata, see our [metadata](/runs/metadata) guide. ### Progress monitoring This example demonstrates how to create a progress monitor component that can be used to display the progress of a run: ```tsx "use client"; // This is needed for Next.js App Router or other RSC frameworks import { useRealtimeRun } from "@trigger.dev/react-hooks"; export function ProgressMonitor({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error, isLoading } = useRealtimeRun(runId, { accessToken: publicAccessToken, }); if (isLoading) return
Loading run...
; if (error) return
Error: {error.message}
; if (!run) return
Run not found
; const progress = run.metadata?.progress as | { current: number; total: number; percentage: number; currentItem: string; } | undefined; return (

Run Status: {run.status}

Run ID: {run.id}

{progress && (
Progress {progress.percentage}%

Processing: {progress.currentItem} ({progress.current}/{progress.total})

)}
); } ``` ### Reusable progress bar This example demonstrates how to create a reusable progress bar component that can be used to display the percentage progress of a run: ```tsx "use client"; import { useRealtimeRun } from "@trigger.dev/react-hooks"; interface ProgressBarProps { runId: string; publicAccessToken: string; title?: string; } export function ProgressBar({ runId, publicAccessToken, title }: ProgressBarProps) { const { run } = useRealtimeRun(runId, { accessToken: publicAccessToken, }); const progress = run?.metadata?.progress as | { current?: number; total?: number; percentage?: number; currentItem?: string; } | undefined; const percentage = progress?.percentage ?? 0; const isComplete = run?.status === "COMPLETED"; const isFailed = run?.status === "FAILED"; return (
{title &&

{title}

}
{progress?.current && progress?.total ? `${progress.current}/${progress.total} items` : "Processing..."} {percentage}%
{progress?.currentItem && (

Current: {progress.currentItem}

)}
); } ``` ### Status indicator with logs This example demonstrates how to create a status indicator component that can be used to display the status of a run, and also logs that are emitted by the task: ```tsx "use client"; import { useRealtimeRun } from "@trigger.dev/react-hooks"; interface StatusIndicatorProps { runId: string; publicAccessToken: string; } export function StatusIndicator({ runId, publicAccessToken }: StatusIndicatorProps) { const { run } = useRealtimeRun(runId, { accessToken: publicAccessToken, }); const status = run?.metadata?.status as string | undefined; const logs = run?.metadata?.logs as string[] | undefined; const getStatusColor = (status: string | undefined) => { switch (status) { case "completed": return "text-green-600 bg-green-100"; case "failed": return "text-red-600 bg-red-100"; case "running": return "text-blue-600 bg-blue-100"; default: return "text-gray-600 bg-gray-100"; } }; return (
{status || run?.status || "Unknown"} Run {run?.id}
{logs && logs.length > 0 && (

Logs

{logs.map((log, index) => (
{log}
))}
)}
); } ``` ### Multi-stage deployment monitor This example demonstrates how to create a multi-stage deployment monitor component that can be used to display the progress of a deployment: ```tsx "use client"; import { useRealtimeRun } from "@trigger.dev/react-hooks"; interface DeploymentMonitorProps { runId: string; publicAccessToken: string; } const DEPLOYMENT_STAGES = [ "initializing", "building", "testing", "deploying", "verifying", "completed", ] as const; export function DeploymentMonitor({ runId, publicAccessToken }: DeploymentMonitorProps) { const { run } = useRealtimeRun(runId, { accessToken: publicAccessToken, }); const status = run?.metadata?.status as string | undefined; const logs = run?.metadata?.logs as string[] | undefined; const currentStageIndex = DEPLOYMENT_STAGES.indexOf(status as any); return (

Deployment Progress

{/* Stage indicators */}
{DEPLOYMENT_STAGES.map((stage, index) => { const isActive = currentStageIndex === index; const isCompleted = currentStageIndex > index; const isFailed = run?.status === "FAILED" && currentStageIndex === index; return (
{isCompleted ? "✓" : index + 1}
{stage} {isActive && (
)}
); })}
{/* Recent logs */} {logs && logs.length > 0 && (
{logs.slice(-5).map((log, index) => (
$ {log}
))}
)}
); } ``` ### Type safety Define TypeScript interfaces for your metadata to get full type safety: ```tsx "use client"; import { useRealtimeRun } from "@trigger.dev/react-hooks"; interface TaskMetadata { progress?: { current: number; total: number; percentage: number; currentItem: string; }; status?: "initializing" | "processing" | "completed" | "failed"; user?: { id: string; name: string; }; logs?: string[]; } export function TypedMetadataComponent({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run } = useRealtimeRun(runId, { accessToken: publicAccessToken, }); // Type-safe metadata access const metadata = run?.metadata as TaskMetadata | undefined; return (
{metadata?.progress &&

Progress: {metadata.progress.percentage}%

} {metadata?.user && (

User: {metadata.user.name} ({metadata.user.id})

)} {metadata?.status &&

Status: {metadata.status}

}
); } ``` ## Common options ### accessToken & baseURL You can pass the `accessToken` option to the Realtime hooks to authenticate the subscription. ```tsx import { useRealtimeRun } from "@trigger.dev/react-hooks"; export function MyComponent({ runId, publicAccessToken, }: { runId: string; publicAccessToken: string; }) { const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken, baseURL: "https://my-self-hosted-trigger.com", // Optional if you are using a self-hosted Trigger.dev instance }); if (error) return
Error: {error.message}
; return
Run: {run.id}
; } ``` ### enabled You can pass the `enabled` option to the Realtime hooks to enable or disable the subscription. ```tsx import { useRealtimeRun } from "@trigger.dev/react-hooks"; export function MyComponent({ runId, publicAccessToken, enabled, }: { runId: string; publicAccessToken: string; enabled: boolean; }) { const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken, enabled, }); if (error) return
Error: {error.message}
; return
Run: {run.id}
; } ``` This allows you to conditionally disable using the hook based on some state. ### id You can pass the `id` option to the Realtime hooks to change the ID of the subscription. ```tsx import { useRealtimeRun } from "@trigger.dev/react-hooks"; export function MyComponent({ id, runId, publicAccessToken, enabled, }: { id: string; runId: string; publicAccessToken: string; enabled: boolean; }) { const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken, enabled, id, }); if (error) return
Error: {error.message}
; return
Run: {run.id}
; } ``` This allows you to change the ID of the subscription based on some state. Passing in a different ID will unsubscribe from the current subscription and subscribe to the new one (and remove any cached data). ## Frequently asked questions ### How do I show a progress bar for a background task in React? Use `metadata.set()` inside your task to update a progress value, then read it with `useRealtimeRun` in your component. The hook re-renders your component on every metadata change. See [Using metadata to show progress](#using-metadata-to-show-progress-in-your-ui) above for a complete example. ### What's the difference between run updates and streaming? Run updates (this page) give you **run state**: status, metadata, and tags. They're for progress bars, status badges, and dashboards. [Streaming](/realtime/react-hooks/streams) gives you **continuous data** like AI tokens or file chunks. Use run updates for "how far along is my task?" and streaming for "show me the output as it generates." ### Can I subscribe to multiple runs at once? Yes. Use `useRealtimeRunsWithTag` to subscribe to all runs with a specific tag (e.g., `user:123`), or `useRealtimeBatch` for all runs in a batch. Each yields an array of run objects that update in real time. ### Do I need to set up polling or WebSockets? No. The hooks handle the connection automatically using Trigger.dev's Realtime infrastructure (built on [Electric SQL](/realtime/how-it-works)). Just pass a run ID and an access token.