diff --git a/.changeset/nine-vans-perform.md b/.changeset/nine-vans-perform.md new file mode 100644 index 0000000000..89049dd325 --- /dev/null +++ b/.changeset/nine-vans-perform.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/react-hooks": patch +--- + +Add trigger options to all trigger hooks diff --git a/docs/frontend/overview.mdx b/docs/frontend/overview.mdx index e972a34eca..ad0721e471 100644 --- a/docs/frontend/overview.mdx +++ b/docs/frontend/overview.mdx @@ -89,35 +89,6 @@ const publicToken = await auth.createPublicToken({ }); ``` -### Write scopes - -You can also specify write scopes, which is required for triggering tasks from your frontend application: - -```ts -const publicToken = await auth.createPublicToken({ - scopes: { - write: { - tasks: ["my-task-1", "my-task-2"], - }, - }, -}); -``` - -This will allow the token to trigger the specified tasks. `tasks` is the only write scope available at the moment. - -We **strongly** recommend creating short-lived tokens for write scopes, as they can be used to trigger tasks from your frontend application: - -```ts -const publicToken = await auth.createPublicToken({ - scopes: { - write: { - tasks: ["my-task-1"], // ✅ this token can trigger this task - }, - }, - expirationTime: "1m", // ✅ this token will expire after 1 minute -}); -``` - ### Expiration By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token: diff --git a/docs/frontend/react-hooks.mdx b/docs/frontend/react-hooks.mdx deleted file mode 100644 index 3ce859db08..0000000000 --- a/docs/frontend/react-hooks.mdx +++ /dev/null @@ -1,797 +0,0 @@ ---- -title: React hooks -sidebarTitle: React hooks -description: Using the Trigger.dev v3 API from your React application. ---- - -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](/frontend/overview). 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: - - - -```bash npm -npm add @trigger.dev/react-hooks -``` - -```bash pnpm -pnpm add @trigger.dev/react-hooks -``` - -```bash yarn -yarn install @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](/frontend/overview). - -```tsx -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 - -```tsx -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) { - return ( - - - - ); -} -``` - -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. - -```tsx -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) { - return ( - - - - ); -} -``` - -### 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: - -```tsx app/page.tsx -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export default function Page() { - return ( - - - - ); -} -``` - -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: - -```ts components/TriggerProvider.tsx -"use client"; - -import { TriggerAuthContext } from "@trigger.dev/react-hooks"; - -export function TriggerProvider({ - accessToken, - children, -}: { - accessToken: string; - children: React.ReactNode; -}) { - return ( - - {children} - - ); -} -``` - -### 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. - -```tsx 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("example", { foo: "bar" }); - - // Set the auto-generated publicAccessToken in a cookie - cookies().set("publicAccessToken", handle.publicAccessToken); // ✅ this token only has access to read this run - - 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`. - -```tsx pages/runs/[id].tsx -import { TriggerProvider } from "@/components/TriggerProvider"; - -export default function RunPage({ params }: { params: { id: string } }) { - const publicAccessToken = cookies().get("publicAccessToken"); - - return ( - - - - ); -} -``` - -Instead of a cookie, you could also use a query parameter to pass the token to the frontend: - -```tsx 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("example", { foo: "bar" }); - - redirect(`/runs/${handle.id}?publicAccessToken=${handle.publicAccessToken}`); -} -``` - -And then in the `/runs/[id].tsx` page: - -```tsx pages/runs/[id].tsx -import { TriggerProvider } from "@/components/TriggerProvider"; - -export default function RunPage({ - params, - searchParams, -}: { - params: { id: string }; - searchParams: { publicAccessToken: string }; -}) { - return ( - - - - ); -} -``` - -Another alternative would be to use a server-side rendered page to fetch the token and pass it to the frontend: - - - -```tsx pages/runs/[id].tsx -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 ( - - - - ); -} -``` - -```tsx trigger/auth.ts -import { auth } from "@trigger.dev/sdk/v3"; - -export async function generatePublicAccessToken(runId: string) { - return auth.createPublicToken({ - scopes: { - read: { - runs: [runId], - }, - }, - expirationTime: "1h", - }); -} -``` - - - -## SWR vs Realtime hooks - -We offer two "styles" of hooks: SWR and Realtime. The SWR hooks use the [swr](https://swr.vercel.app/) library to fetch data once and cache it. The Realtime hooks use [Trigger.dev realtime](/realtime) to subscribe to updates in real-time. - - - It can be a little confusing which one to use because [swr](https://swr.vercel.app/) 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. - -```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}
; -} -``` - -See our [Realtime documentation](/realtime) 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. - -```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}
- ))} -
- ); -} -``` - -See our [Realtime documentation](/realtime) for more information. - -### 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. - -### 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](/realtime#streams) for more information about emitting streams from a task. - -```tsx -"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
Error: {error.message}
; - - return ( -
-
Run: {run.id}
-
- {Object.keys(streams).map((stream) => ( -
Stream: {stream}
- ))} -
-
- ); -} -``` - -You can provide the type of the streams to the `useRealtimeRunWithStreams` hook: - -```tsx -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(runId, { - accessToken: publicAccessToken, - }); - - if (error) return
Error: {error.message}
; - - const text = streams.openai?.map((part) => part).join(""); - - return ( -
-
Run: {run.id}
-
{text}
-
- ); -} -``` - -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: - -```tsx -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(runId, { - accessToken: publicAccessToken, - }); - - if (error) return
Error: {error.message}
; - - const text = streams.openai - ?.filter((stream) => stream.type === "text-delta") - ?.map((part) => part.text) - .join(""); - - return ( -
-
Run: {run.id}
-
{text}
-
- ); -} -``` - -### Common options - -#### 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). - -#### 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. - -```tsx -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
Error: {error.message}
; - - return ( -
- {runs.map((run) => ( -
Run: {run.id}
- ))} -
- ); -} -``` - -## SWR Hooks - -### useRun - -The `useRun` hook allows you to fetch a run by its ID. - -```tsx -"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
Loading...
; - if (error) return
Error: {error.message}
; - - return
Run: {run.id}
; -} -``` - -The `run` object returned is the same as the [run object](/management/runs/retrieve) 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: - -```tsx -import { useRun } from "@trigger.dev/react-hooks"; -import type { myTask } from "@/trigger/myTask"; - -export function MyComponent({ runId }: { runId: string }) { - const { run, error, isLoading } = useRun(runId, { - refreshInterval: 0, // Disable polling - }); - - if (isLoading) return
Loading...
; - if (error) return
Error: {error.message}
; - - // Now run.payload and run.output are correctly typed - - return
Run: {run.id}
; -} -``` - -### Common options - -You can pass the following options to the all SWR hooks: - - - Revalidate the data when the window regains focus. - - - - Revalidate the data when the browser regains a network connection. - - - - 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 - - - An error object if an error occurred while fetching the data. - - - - A boolean indicating if the data is currently being fetched. - - - - A boolean indicating if the data is currently being revalidated. - - - - 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](/frontend/overview#write-scopes) for more information. - -### useTaskTrigger - -The `useTaskTrigger` hook allows you to trigger a task from your frontend application. - -```tsx -"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("my-task", { - accessToken: publicAccessToken, - }); - - if (error) { - return
Error: {error.message}
; - } - - if (handle) { - return
Run ID: {handle.id}
; - } - - return ( - - ); -} -``` - -### useRealtimeTaskTrigger - -The `useRealtimeTaskTrigger` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime: - -```tsx -"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("my-task", { - accessToken: publicAccessToken, - }); - - if (error) { - return
Error: {error.message}
; - } - - // This is the realtime run object, which will automatically update when the run changes - if (run) { - return
Run ID: {run.id}
; - } - - return ( - - ); -} -``` - -### 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. - -```tsx -"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
Error: {error.message}
; - } - - if (streams && run) { - const text = streams.openai?.map((part) => part).join(""); - - return ( -
-
Run ID: {run.id}
-
{text}
-
- ); - } - - return ( - - ); -} -``` diff --git a/docs/frontend/react-hooks/overview.mdx b/docs/frontend/react-hooks/overview.mdx new file mode 100644 index 0000000000..da4286c95c --- /dev/null +++ b/docs/frontend/react-hooks/overview.mdx @@ -0,0 +1,333 @@ +--- +title: Overview +sidebarTitle: Overview +description: Using the Trigger.dev v3 API from your React application. +--- + +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](/frontend/overview). You can use these hooks to fetch runs, and subscribe to real-time updates, and trigger tasks from your frontend application. + +## Installation + +Install the `@trigger.dev/react-hooks` package in your project: + + + +```bash npm +npm add @trigger.dev/react-hooks +``` + +```bash pnpm +pnpm add @trigger.dev/react-hooks +``` + +```bash yarn +yarn install @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](/frontend/overview). + +```tsx +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 + +```tsx +import { TriggerAuthContext } from "@trigger.dev/react-hooks"; + +export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) { + return ( + + + + ); +} +``` + +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. + +```tsx +import { TriggerAuthContext } from "@trigger.dev/react-hooks"; + +export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) { + return ( + + + + ); +} +``` + +### 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: + +```tsx app/page.tsx +import { TriggerAuthContext } from "@trigger.dev/react-hooks"; + +export default function Page() { + return ( + + + + ); +} +``` + +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: + +```ts components/TriggerProvider.tsx +"use client"; + +import { TriggerAuthContext } from "@trigger.dev/react-hooks"; + +export function TriggerProvider({ + accessToken, + children, +}: { + accessToken: string; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} +``` + +### 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. + +```tsx 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("example", { foo: "bar" }); + + // Set the auto-generated publicAccessToken in a cookie + cookies().set("publicAccessToken", handle.publicAccessToken); // ✅ this token only has access to read this run + + 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`. + +```tsx pages/runs/[id].tsx +import { TriggerProvider } from "@/components/TriggerProvider"; + +export default function RunPage({ params }: { params: { id: string } }) { + const publicAccessToken = cookies().get("publicAccessToken"); + + return ( + + + + ); +} +``` + +Instead of a cookie, you could also use a query parameter to pass the token to the frontend: + +```tsx 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("example", { foo: "bar" }); + + redirect(`/runs/${handle.id}?publicAccessToken=${handle.publicAccessToken}`); +} +``` + +And then in the `/runs/[id].tsx` page: + +```tsx pages/runs/[id].tsx +import { TriggerProvider } from "@/components/TriggerProvider"; + +export default function RunPage({ + params, + searchParams, +}: { + params: { id: string }; + searchParams: { publicAccessToken: string }; +}) { + return ( + + + + ); +} +``` + +Another alternative would be to use a server-side rendered page to fetch the token and pass it to the frontend: + + + +```tsx pages/runs/[id].tsx +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 ( + + + + ); +} +``` + +```tsx trigger/auth.ts +import { auth } from "@trigger.dev/sdk/v3"; + +export async function generatePublicAccessToken(runId: string) { + return auth.createPublicToken({ + scopes: { + read: { + runs: [runId], + }, + }, + expirationTime: "1h", + }); +} +``` + + + +## SWR vs Realtime hooks + +We offer two "styles" of hooks: SWR and Realtime. The SWR hooks use the [swr](https://swr.vercel.app/) library to fetch data once and cache it. The Realtime hooks use [Trigger.dev realtime](/realtime) to subscribe to updates in real-time. + + + It can be a little confusing which one to use because [swr](https://swr.vercel.app/) 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. + + +## SWR Hooks + +### useRun + +The `useRun` hook allows you to fetch a run by its ID. + +```tsx +"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
Loading...
; + if (error) return
Error: {error.message}
; + + return
Run: {run.id}
; +} +``` + +The `run` object returned is the same as the [run object](/management/runs/retrieve) 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: + +```tsx +import { useRun } from "@trigger.dev/react-hooks"; +import type { myTask } from "@/trigger/myTask"; + +export function MyComponent({ runId }: { runId: string }) { + const { run, error, isLoading } = useRun(runId, { + refreshInterval: 0, // Disable polling + }); + + if (isLoading) return
Loading...
; + if (error) return
Error: {error.message}
; + + // Now run.payload and run.output are correctly typed + + return
Run: {run.id}
; +} +``` + +### Common options + +You can pass the following options to the all SWR hooks: + + + Revalidate the data when the window regains focus. + + + + Revalidate the data when the browser regains a network connection. + + + + 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 + + + An error object if an error occurred while fetching the data. + + + + A boolean indicating if the data is currently being fetched. + + + + A boolean indicating if the data is currently being revalidated. + + + + A boolean indicating if an error occurred while fetching the data. + + +## Realtime hooks + +See our [Realtime hooks documentation](/frontend/react-hooks/realtime) for more information. + +## Trigger Hooks + +See our [Trigger hooks documentation](/frontend/react-hooks/triggering) for more information. diff --git a/docs/frontend/react-hooks/realtime.mdx b/docs/frontend/react-hooks/realtime.mdx new file mode 100644 index 0000000000..b8f732e940 --- /dev/null +++ b/docs/frontend/react-hooks/realtime.mdx @@ -0,0 +1,391 @@ +--- +title: Realtime hooks +sidebarTitle: Realtime +description: Get live updates from the Trigger.dev API in your frontend application. +--- + +These hooks allow you to subscribe to runs, batches, and streams using [Trigger.dev realtime](/realtime). Before reading this guide: + +- Read our [Realtime documentation](/realtime) to understand how the Trigger.dev realtime API works. +- Read how to [setup and authenticate](/frontend/overview) using the `@trigger.dev/react-hooks` package. + +## 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}
; +} +``` + +See our [Realtime documentation](/realtime) 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. + +```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. + +### 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](/realtime#streams) for more information about emitting streams from a task. + +```tsx +"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
Error: {error.message}
; + + return ( +
+
Run: {run.id}
+
+ {Object.keys(streams).map((stream) => ( +
Stream: {stream}
+ ))} +
+
+ ); +} +``` + +You can provide the type of the streams to the `useRealtimeRunWithStreams` hook: + +```tsx +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(runId, { + accessToken: publicAccessToken, + }); + + if (error) return
Error: {error.message}
; + + const text = streams.openai?.map((part) => part).join(""); + + return ( +
+
Run: {run.id}
+
{text}
+
+ ); +} +``` + +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: + +```tsx +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(runId, { + accessToken: publicAccessToken, + }); + + if (error) return
Error: {error.message}
; + + const text = streams.openai + ?.filter((stream) => stream.type === "text-delta") + ?.map((part) => part.text) + .join(""); + + return ( +
+
Run: {run.id}
+
{text}
+
+ ); +} +``` + +## 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). + +### 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. + +```tsx +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
Error: {error.message}
; + + return ( +
+ {runs.map((run) => ( +
Run: {run.id}
+ ))} +
+ ); +} +``` diff --git a/docs/frontend/react-hooks/triggering.mdx b/docs/frontend/react-hooks/triggering.mdx new file mode 100644 index 0000000000..9b00d0f295 --- /dev/null +++ b/docs/frontend/react-hooks/triggering.mdx @@ -0,0 +1,250 @@ +--- +title: Trigger hooks +sidebarTitle: Triggering +description: Triggering tasks from your frontend application. +--- + +We provide a set of hooks that can be used to trigger tasks from your frontend application. + +## Demo + +We've created a [Demo application](https://github.com/triggerdotdev/realtime-llm-battle) that demonstrates how to use our React hooks to trigger tasks in a Next.js application. The application uses the `@trigger.dev/react-hooks` package to trigger a task and subscribe to the run in real-time. + +## Installation + +Install the `@trigger.dev/react-hooks` package in your project: + + + +```bash npm +npm add @trigger.dev/react-hooks +``` + +```bash pnpm +pnpm add @trigger.dev/react-hooks +``` + +```bash yarn +yarn install @trigger.dev/react-hooks +``` + + + +## Authentication + +To authenticate a trigger hook, you must provide a special one-time use "trigger" token. These tokens are very similar to [Public Access Tokens](/frontend/overview#authentication), but they can only be used once to trigger a task. You can generate a trigger token using the `auth.createTriggerPublicToken` function in your backend code: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken("my-task"); +``` + +These tokens also expire, with the default expiration time being 15 minutes. You can specify a custom expiration time by passing a `expirationTime` parameter: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken("my-task", { + expirationTime: "24hr", +}); +``` + +You can also pass multiple tasks to the `createTriggerPublicToken` function to create a token that can trigger multiple tasks: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken(["my-task-1", "my-task-2"]); +``` + +You can also pass the `multipleUse` parameter to create a token that can be used multiple times: + +```ts +import { auth } from "@trigger.dev/sdk/v3"; + +// Somewhere in your backend code +const triggerToken = await auth.createTriggerPublicToken("my-task", { + multipleUse: true, // ❌ Use this with caution! +}); +``` + + + After generating the trigger token in your backend, you must pass it to your frontend application. + We have a guide on how to do this in the [React hooks + overview](/frontend/react-hooks/overview#passing-the-token-to-the-frontend). + + +## Hooks + +### useTaskTrigger + +The `useTaskTrigger` hook allows you to trigger a task from your frontend application. + +```tsx +"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"; +// 👆 This is the type of your task + +export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) { + // pass the type of your task here 👇 + const { submit, handle, error, isLoading } = useTaskTrigger("my-task", { + accessToken: publicAccessToken, // 👈 this is the "trigger" token + }); + + if (error) { + return
Error: {error.message}
; + } + + if (handle) { + return
Run ID: {handle.id}
; + } + + return ( + + ); +} +``` + +`useTaskTrigger` returns an object with the following properties: + +- `submit`: A function that triggers the task. It takes the payload of the task as an argument. +- `handle`: The run handle object. This object contains the ID of the run that was triggered, along with a Public Access Token that can be used to access the run. +- `isLoading`: A boolean that indicates whether the task is currently being triggered. +- `error`: An error object that contains any errors that occurred while triggering the task. + +The `submit` function triggers the task with the specified payload. You can additionally pass an optional [options](/triggering#options) argument to the `submit` function: + +```tsx +submit({ foo: "bar" }, { tags: ["tag1", "tag2"] }); +``` + +#### Using the handle object + +You can use the `handle` object to initiate a subsequent [realtime hook](/frontend/react-hooks/realtime#userealtimerun) to subscribe to the run. + +```tsx +"use client"; // This is needed for Next.js App Router or other RSC frameworks + +import { useTaskTrigger, useRealtimeRun } from "@trigger.dev/react-hooks"; +import type { myTask } from "@/trigger/myTask"; +// 👆 This is the type of your task + +export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) { + // pass the type of your task here 👇 + const { submit, handle, error, isLoading } = useTaskTrigger("my-task", { + accessToken: publicAccessToken, // 👈 this is the "trigger" token + }); + + // use the handle object to preserve type-safety 👇 + const { run, error: realtimeError } = useRealtimeRun(handle, { + accessToken: handle?.publicAccessToken, + enabled: !!handle, // Only subscribe to the run if the handle is available + }); + + if (error) { + return
Error: {error.message}
; + } + + if (handle) { + return
Run ID: {handle.id}
; + } + + if (realtimeError) { + return
Error: {realtimeError.message}
; + } + + if (run) { + return
Run ID: {run.id}
; + } + + return ( + + ); +} +``` + +We've also created some additional hooks that allow you to trigger tasks and subscribe to the run in one step: + +### useRealtimeTaskTrigger + +The `useRealtimeTaskTrigger` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime: + +```tsx +"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("my-task", { + accessToken: publicAccessToken, + }); + + if (error) { + return
Error: {error.message}
; + } + + // This is the realtime run object, which will automatically update when the run changes + if (run) { + return
Run ID: {run.id}
; + } + + return ( + + ); +} +``` + +### 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. + +```tsx +"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
Error: {error.message}
; + } + + if (streams && run) { + const text = streams.openai?.map((part) => part).join(""); + + return ( +
+
Run ID: {run.id}
+
{text}
+
+ ); + } + + return ( + + ); +} +``` diff --git a/docs/mint.json b/docs/mint.json index cca95a2d01..0154122ef1 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -110,6 +110,10 @@ { "source": "/runs-and-attempts", "destination": "/runs" + }, + { + "source": "/frontend/react-hooks", + "destination": "/frontend/react-hooks/overview" } ], "anchors": [ @@ -207,7 +211,14 @@ "group": "Frontend usage", "pages": [ "frontend/overview", - "frontend/react-hooks" + { + "group": "React hooks", + "pages": [ + "frontend/react-hooks/overview", + "frontend/react-hooks/realtime", + "frontend/react-hooks/triggering" + ] + } ] }, { diff --git a/docs/triggering.mdx b/docs/triggering.mdx index 118223961a..f5e251fec7 100644 --- a/docs/triggering.mdx +++ b/docs/triggering.mdx @@ -699,7 +699,7 @@ export const childTask2 = task({ ## Triggering from your frontend If you want to trigger a task directly from a frontend application, you can use our [React -hooks](/frontend/react-hooks#trigger-hooks). +hooks](/frontend/react-hooks/triggering). ## Options diff --git a/packages/react-hooks/src/hooks/useTaskTrigger.ts b/packages/react-hooks/src/hooks/useTaskTrigger.ts index 2878d06a88..a65c274d4b 100644 --- a/packages/react-hooks/src/hooks/useTaskTrigger.ts +++ b/packages/react-hooks/src/hooks/useTaskTrigger.ts @@ -8,7 +8,7 @@ import { makeIdempotencyKey, RunHandleFromTypes, stringifyIO, - TriggerOptions, + type TriggerOptions, } from "@trigger.dev/core/v3"; import useSWRMutation from "swr/mutation"; import { useApiClient, UseApiClientOptions } from "./useApiClient.js"; @@ -118,7 +118,7 @@ export type RealtimeTriggerInstanceWithStreams< TTask extends AnyTask, TStreams extends Record = Record, > = UseRealtimeRunWithStreamsInstance & { - submit: (payload: TaskPayload) => void; + submit: (payload: TaskPayload, options?: TriggerOptions) => void; isLoading: boolean; handle?: RunHandleFromTypes>; }; @@ -166,7 +166,7 @@ export function useRealtimeTaskTriggerWithStreams< } export type RealtimeTriggerInstance = UseRealtimeRunInstance & { - submit: (payload: TaskPayload) => void; + submit: (payload: TaskPayload, options?: TriggerOptions) => void; isLoading: boolean; handle?: RunHandleFromTypes>; };