Files
triggerdotdev--trigger.dev/docs/frontend/react-hooks.mdx
T
Eric Allam 3252d9ec31 docs: realtime, react hooks, and frontend usage (#1434)
* WIP realtime/frontend docs

* WIP

* Deleted old examples

* Improved styling

* Improved the generated example styles

* More style improvements

* More realtime docs

* Couple doc tweeks

* runs-and-attempts -> runs

* Remove the prerelease package version

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2024-10-29 12:31:04 +00:00

447 lines
12 KiB
Plaintext

---
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:
<CodeGroup>
```bash npm
npm add @trigger.dev/react-hooks
```
```bash pnpm
pnpm add @trigger.dev/react-hooks
```
```bash yarn
yarn install @trigger.dev/react-hooks
```
</CodeGroup>
## Authentication
Before you can use the hooks, you need to provide a public access token to the `TriggerAuthContext` provider. Learn more about [authentication in the frontend guide](/frontend/overview).
```tsx
import { TriggerAuthContext } from "@trigger.dev/react-hooks";
export function SetupTrigger() {
return (
<TriggerAuthContext.Provider value={{ accessToken: "your-access-token" }}>
<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.
```tsx
import { TriggerAuthContext } from "@trigger.dev/react-hooks";
export function SetupTrigger() {
return (
<TriggerAuthContext.Provider
value={{
accessToken: "your-access-token",
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:
```tsx 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:
```ts 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.
```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<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`.
```tsx 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:
```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<typeof exampleTask>("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 (
<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:
<CodeGroup>
```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 (
<TriggerProvider accessToken={publicAccessToken}>
<RunDetails id={params.id} />
</TriggerProvider>
);
}
```
```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",
});
}
```
</CodeGroup>
## Usage
### 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.
<Note>
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.
</Note>
All hooks named `useRealtime*` are Realtime hooks, and all hooks named `use*` are SWR hooks.
#### Common SWR hook options
You can pass the following options to the all SWR hooks:
<ParamField path="revalidateOnFocus" type="boolean">
Revalidate the data when the window regains focus.
</ParamField>
<ParamField path="revalidateOnReconnect" type="boolean">
Revalidate the data when the browser regains a network connection.
</ParamField>
<ParamField path="refreshInterval" type="number">
Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most
use-cases. Use the Realtime hooks instead.
</ParamField>
#### Common SWR hook return values
<ResponseField name="error" type="Error">
An error object if an error occurred while fetching the data.
</ResponseField>
<ResponseField name="isLoading" type="boolean">
A boolean indicating if the data is currently being fetched.
</ResponseField>
<ResponseField name="isValidating" type="boolean">
A boolean indicating if the data is currently being revalidated.
</ResponseField>
<ResponseField name="isError" type="boolean">
A boolean indicating if an error occurred while fetching the data.
</ResponseField>
### 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 <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](/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<typeof myTask>(runId);
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>;
}
```
### 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 }: { runId: string }) {
const { run, error } = useRealtimeRun(runId);
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:
```tsx
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ runId }: { runId: string }) {
const { run, error } = useRealtimeRun<typeof myTask>(runId);
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](/realtime) for more information.
### 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 <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:
```tsx
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:
```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<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](/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 <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
See our [Realtime documentation](/realtime) for more information.