Our react hooks package provides a set of hooks that make it easy to interact with the Trigger.dev API from your React application, using our frontend API. You can use these hooks to fetch runs, batches, and subscribe to real-time updates.

Installation

Install the @trigger.dev/react-hooks package in your project:

npm add @trigger.dev/react-hooks

Authentication

All hooks accept an optional last argument options that accepts an accessToken param, which should be a valid Public Access Token. Learn more about generating tokens in the frontend guide.

import { useRealtimeRun } from "@trigger.dev/react-hooks";

export function MyComponent({
  runId,
  publicAccessToken,
}: {
  runId: string;
  publicAccessToken: string;
}) {
  const { run, error } = useRealtimeRun(runId, {
    accessToken: publicAccessToken, // This is required
    baseURL: "https://your-trigger-dev-instance.com", // optional, only needed if you are self-hosting Trigger.dev
  });

  // ...
}

Alternatively, you can use our TriggerAuthContext provider

import { TriggerAuthContext } from "@trigger.dev/react-hooks";

export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) {
  return (
    <TriggerAuthContext.Provider value={{ accessToken: publicAccessToken }}>
      <MyComponent />
    </TriggerAuthContext.Provider>
  );
}

Now children components can use the hooks to interact with the Trigger.dev API. If you are self-hosting Trigger.dev, you can provide the baseURL to the TriggerAuthContext provider.

import { TriggerAuthContext } from "@trigger.dev/react-hooks";

export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) {
  return (
    <TriggerAuthContext.Provider
      value={{
        accessToken: publicAccessToken,
        baseURL: "https://your-trigger-dev-instance.com",
      }}
    >
      <MyComponent />
    </TriggerAuthContext.Provider>
  );
}

Next.js and client components

If you are using Next.js with the App Router, you have to make sure the component that uses the TriggerAuthContext is a client component. So for example, the following code will not work:

app/page.tsx
import { TriggerAuthContext } from "@trigger.dev/react-hooks";

export default function Page() {
  return (
    <TriggerAuthContext.Provider value={{ accessToken: "your-access-token" }}>
      <MyComponent />
    </TriggerAuthContext.Provider>
  );
}

That’s because Page is a server component and the TriggerAuthContext.Provider uses client-only react code. To fix this, wrap the TriggerAuthContext.Provider in a client component:

components/TriggerProvider.tsx
"use client";

import { TriggerAuthContext } from "@trigger.dev/react-hooks";

export function TriggerProvider({
  accessToken,
  children,
}: {
  accessToken: string;
  children: React.ReactNode;
}) {
  return (
    <TriggerAuthContext.Provider
      value={{
        accessToken,
      }}
    >
      {children}
    </TriggerAuthContext.Provider>
  );
}

Passing the token to the frontend

Techniques for passing the token to the frontend vary depending on your setup. Here are a few ways to do it for different setups:

Next.js App Router

If you are using Next.js with the App Router and you are triggering a task from a server action, you can use cookies to store and pass the token to the frontend.

actions/trigger.ts
"use server";

import { tasks } from "@trigger.dev/sdk/v3";
import type { exampleTask } from "@/trigger/example";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";

export async function startRun() {
  const handle = await tasks.trigger<typeof exampleTask>("example", { foo: "bar" });

  // Set the auto-generated publicAccessToken in a cookie
  cookies().set("publicAccessToken", handle.publicAccessToken);

  redirect(`/runs/${handle.id}`);
}

Then in the /runs/[id].tsx page, you can read the token from the cookie and pass it to the TriggerProvider.

pages/runs/[id].tsx
import { TriggerProvider } from "@/components/TriggerProvider";

export default function RunPage({ params }: { params: { id: string } }) {
  const publicAccessToken = cookies().get("publicAccessToken");

  return (
    <TriggerProvider accessToken={publicAccessToken}>
      <RunDetails id={params.id} />
    </TriggerProvider>
  );
}

Instead of a cookie, you could also use a query parameter to pass the token to the frontend:

actions/trigger.ts
import { tasks } from "@trigger.dev/sdk/v3";
import type { exampleTask } from "@/trigger/example";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";

export async function startRun() {
  const handle = await tasks.trigger<typeof exampleTask>("example", { foo: "bar" });

  redirect(`/runs/${handle.id}?publicAccessToken=${handle.publicAccessToken}`);
}

And then in the /runs/[id].tsx page:

pages/runs/[id].tsx
import { TriggerProvider } from "@/components/TriggerProvider";

export default function RunPage({
  params,
  searchParams,
}: {
  params: { id: string };
  searchParams: { publicAccessToken: string };
}) {
  return (
    <TriggerProvider accessToken={searchParams.publicAccessToken}>
      <RunDetails id={params.id} />
    </TriggerProvider>
  );
}

Another alternative would be to use a server-side rendered page to fetch the token and pass it to the frontend:

import { TriggerProvider } from "@/components/TriggerProvider";
import { generatePublicAccessToken } from "@/trigger/auth";

export default async function RunPage({ params }: { params: { id: string } }) {
  // This will be executed on the server only
  const publicAccessToken = await generatePublicAccessToken(params.id);

  return (
    <TriggerProvider accessToken={publicAccessToken}>
      <RunDetails id={params.id} />
    </TriggerProvider>
  );
}

SWR vs Realtime hooks

We offer two “styles” of hooks: SWR and Realtime. The SWR hooks use the swr library to fetch data once and cache it. The Realtime hooks use Trigger.dev realtime to subscribe to updates in real-time.

It can be a little confusing which one to use because swr can also be configured to poll for updates. But because of rate-limits and the way the Trigger.dev API works, we recommend using the Realtime hooks for most use-cases.

All hooks named useRealtime* are Realtime hooks, and all hooks named use* are SWR hooks.

Realtime hooks

useRealtimeRun

The useRealtimeRun hook allows you to subscribe to a run by its ID.

"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 <div>Error: {error.message}</div>;

  return <div>Run: {run.id}</div>;
}

To correctly type the run’s payload and output, you can provide the type of your task to the useRealtimeRun hook:

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<typeof myTask>(runId, {
    accessToken: publicAccessToken,
  });

  if (error) return <div>Error: {error.message}</div>;

  // Now run.payload and run.output are correctly typed

  return <div>Run: {run.id}</div>;
}

See our Realtime documentation for more information about the type of the run object and more.

useRealtimeRunsWithTag

The useRealtimeRunsWithTag hook allows you to subscribe to multiple runs with a specific tag.

"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 <div>Error: {error.message}</div>;

  return (
    <div>
      {runs.map((run) => (
        <div key={run.id}>Run: {run.id}</div>
      ))}
    </div>
  );
}

To correctly type the runs payload and output, you can provide the type of your task to the useRealtimeRunsWithTag hook:

import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function MyComponent({ tag }: { tag: string }) {
  const { runs, error } = useRealtimeRunsWithTag<typeof myTask>(tag);

  if (error) return <div>Error: {error.message}</div>;

  // Now runs[i].payload and runs[i].output are correctly typed

  return (
    <div>
      {runs.map((run) => (
        <div key={run.id}>Run: {run.id}</div>
      ))}
    </div>
  );
}

If useRealtimeRunsWithTag could return multiple different types of tasks, you can pass a union of all the task types to the hook:

import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
import type { myTask1, myTask2 } from "@/trigger/myTasks";

export function MyComponent({ tag }: { tag: string }) {
  const { runs, error } = useRealtimeRunsWithTag<typeof myTask1 | typeof myTask2>(tag);

  if (error) return <div>Error: {error.message}</div>;

  // 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 (
    <div>
      {runs.map((run) => (
        <div key={run.id}>Run: {run.id}</div>
      ))}
    </div>
  );
}

See our Realtime documentation for more information.

useRealtimeBatch

The useRealtimeBatch hook allows you to subscribe to a batch of runs by its the batch ID.

"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 <div>Error: {error.message}</div>;

  return (
    <div>
      {runs.map((run) => (
        <div key={run.id}>Run: {run.id}</div>
      ))}
    </div>
  );
}

See our Realtime documentation for more information.

useRealtimeRunWithStreams

The useRealtimeRunWithStreams hook allows you to subscribe to a run by its ID and also receive any streams that are emitted by the task. See our Realtime documentation for more information about emitting streams from a task.

"use client"; // This is needed for Next.js App Router or other RSC frameworks

import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";

export function MyComponent({
  runId,
  publicAccessToken,
}: {
  runId: string;
  publicAccessToken: string;
}) {
  const { run, streams, error } = useRealtimeRunWithStreams(runId, {
    accessToken: publicAccessToken,
  });

  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <div>Run: {run.id}</div>
      <div>
        {Object.keys(streams).map((stream) => (
          <div key={stream}>Stream: {stream}</div>
        ))}
      </div>
    </div>
  );
}

You can provide the type of the streams to the useRealtimeRunWithStreams hook:

import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

type STREAMS = {
  openai: string; // this is the type of each "part" of the stream
};

export function MyComponent({
  runId,
  publicAccessToken,
}: {
  runId: string;
  publicAccessToken: string;
}) {
  const { run, streams, error } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
    accessToken: publicAccessToken,
  });

  if (error) return <div>Error: {error.message}</div>;

  const text = streams.openai?.map((part) => part).join("");

  return (
    <div>
      <div>Run: {run.id}</div>
      <div>{text}</div>
    </div>
  );
}

As you can see above, each stream is an array of the type you provided, keyed by the stream name. If instead of a pure text stream you have a stream of objects, you can provide the type of the object:

import type { TextStreamPart } from "ai";
import type { myTask } from "@/trigger/myTask";

type STREAMS = { openai: TextStreamPart<{}> };

export function MyComponent({
  runId,
  publicAccessToken,
}: {
  runId: string;
  publicAccessToken: string;
}) {
  const { run, streams, error } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
    accessToken: publicAccessToken,
  });

  if (error) return <div>Error: {error.message}</div>;

  const text = streams.openai
    ?.filter((stream) => stream.type === "text-delta")
    ?.map((part) => part.text)
    .join("");

  return (
    <div>
      <div>Run: {run.id}</div>
      <div>{text}</div>
    </div>
  );
}

Common options

enabled

You can pass the enabled option to the Realtime hooks to enable or disable the subscription.

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 <div>Error: {error.message}</div>;

  return <div>Run: {run.id}</div>;
}

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.

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 <div>Error: {error.message}</div>;

  return <div>Run: {run.id}</div>;
}

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).

experimental_throttleInMs

The *withStreams variants of the Realtime hooks accept an experimental_throttleInMs option to throttle the updates from the server. This can be useful if you are getting too many updates and want to reduce the number of updates.

import { useRealtimeRunsWithStreams } from "@trigger.dev/react-hooks";

export function MyComponent({
  runId,
  publicAccessToken,
}: {
  runId: string;
  publicAccessToken: string;
}) {
  const { runs, error } = useRealtimeRunsWithStreams(tag, {
    accessToken: publicAccessToken,
    experimental_throttleInMs: 1000, // Throttle updates to once per second
  });

  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {runs.map((run) => (
        <div key={run.id}>Run: {run.id}</div>
      ))}
    </div>
  );
}

SWR Hooks

useRun

The useRun hook allows you to fetch a run by its ID.

"use client"; // This is needed for Next.js App Router or other RSC frameworks

import { useRun } from "@trigger.dev/react-hooks";

export function MyComponent({ runId }: { runId: string }) {
  const { run, error, isLoading } = useRun(runId);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>Run: {run.id}</div>;
}

The run object returned is the same as the run object returned by the Trigger.dev API. To correctly type the run’s payload and output, you can provide the type of your task to the useRun hook:

import { useRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function MyComponent({ runId }: { runId: string }) {
  const { run, error, isLoading } = useRun<typeof myTask>(runId, {
    refreshInterval: 0, // Disable polling
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  // Now run.payload and run.output are correctly typed

  return <div>Run: {run.id}</div>;
}

Common options

You can pass the following options to the all SWR hooks:

revalidateOnFocus
boolean

Revalidate the data when the window regains focus.

revalidateOnReconnect
boolean

Revalidate the data when the browser regains a network connection.

refreshInterval
number

Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most use-cases. Use the Realtime hooks instead.

Common return values

error
Error

An error object if an error occurred while fetching the data.

isLoading
boolean

A boolean indicating if the data is currently being fetched.

isValidating
boolean

A boolean indicating if the data is currently being revalidated.

isError
boolean

A boolean indicating if an error occurred while fetching the data.

Trigger Hooks

We provide a set of hooks that can be used to trigger tasks from your frontend application. You’ll need to generate a Public Access Token with write permissions to use these hooks. See our frontend guide for more information.

useTaskTrigger

The useTaskTrigger hook allows you to trigger a task from your frontend application.

"use client"; // This is needed for Next.js App Router or other RSC frameworks

import { useTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
  const { submit, handle, error, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
    accessToken: publicAccessToken,
  });

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (handle) {
    return <div>Run ID: {handle.id}</div>;
  }

  return (
    <button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
      {isLoading ? "Loading..." : "Trigger Task"}
    </button>
  );
}

useRealtimeTaskTrigger

The useRealtimeTaskTrigger hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime:

"use client"; // This is needed for Next.js App Router or other RSC frameworks

import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
  const { submit, run, error, isLoading } = useRealtimeTaskTrigger<typeof myTask>("my-task", {
    accessToken: publicAccessToken,
  });

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  // This is the realtime run object, which will automatically update when the run changes
  if (run) {
    return <div>Run ID: {run.id}</div>;
  }

  return (
    <button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
      {isLoading ? "Loading..." : "Trigger Task"}
    </button>
  );
}

useRealtimeTaskTriggerWithStreams

The useRealtimeTaskTriggerWithStreams hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime, and also receive any streams that are emitted by the task.

"use client"; // This is needed for Next.js App Router or other RSC frameworks

import { useRealtimeTaskTriggerWithStreams } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

type STREAMS = {
  openai: string; // this is the type of each "part" of the stream
};

export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
  const { submit, run, streams, error, isLoading } = useRealtimeTaskTriggerWithStreams<
    typeof myTask,
    STREAMS
  >("my-task", {
    accessToken: publicAccessToken,
  });

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (streams && run) {
    const text = streams.openai?.map((part) => part).join("");

    return (
      <div>
        <div>Run ID: {run.id}</div>
        <div>{text}</div>
      </div>
    );
  }

  return (
    <button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
      {isLoading ? "Loading..." : "Trigger Task"}
    </button>
  );
}