> ## Documentation Index
> Fetch the complete documentation index at: https://docs.traffical.io/llms.txt
> Use this file to discover all available pages before exploring further.

# React SDK

> React provider and useTraffical hook for parameter resolution, event tracking, and SSR hydration.

`@traffical/react` provides a context provider and hooks for resolving parameters and tracking events in React apps. It wraps `@traffical/js-client`, so everything the browser client supports works here too.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @traffical/react
  ```

  ```bash pnpm theme={null}
  pnpm add @traffical/react
  ```

  ```bash yarn theme={null}
  yarn add @traffical/react
  ```
</CodeGroup>

## Setup

Wrap your app with `TrafficalProvider`:

```tsx theme={null}
import { TrafficalProvider } from "@traffical/react";

function App() {
  return (
    <TrafficalProvider
      config={{
        orgId: "org_acme",
        projectId: "proj_marketplace",
        env: "production",
        apiKey: process.env.NEXT_PUBLIC_TRAFFICAL_API_KEY!,
      }}
    >
      <MyApp />
    </TrafficalProvider>
  );
}
```

Use an **SDK key** (`traffical_sk_...`, scopes `sdk:read`+`sdk:write`) in browser code — it is browser-safe.

## The `useTraffical` hook

Use `useTraffical` in any component to resolve parameters and track events:

```tsx theme={null}
import { useTraffical } from "@traffical/react";

function CheckoutButton() {
  const { params, track } = useTraffical({
    defaults: {
      "checkout.button.color": "#1E6EFB",
      "checkout.button.label": "Buy now",
    },
  });

  return (
    <button
      style={{ backgroundColor: params["checkout.button.color"] }}
      onClick={() => track("cta_click")}
    >
      {params["checkout.button.label"]}
    </button>
  );
}
```

### Hook options

| Option     | Type                             | Description                                                       |
| ---------- | -------------------------------- | ----------------------------------------------------------------- |
| `defaults` | `Record<string, ParameterValue>` | Default values for the parameters this component reads. Required. |
| `context`  | `Context`                        | Optional extra context merged with provider context.              |
| `tracking` | `"full" \| "decision" \| "none"` | Tracking mode (see below). Default `"full"`.                      |

### Hook return value

| Field           | Type                             | Description                                          |
| --------------- | -------------------------------- | ---------------------------------------------------- |
| `params`        | `Record<string, ParameterValue>` | Resolved parameter values                            |
| `decision`      | `DecisionResult \| null`         | Full decision record (decisionId, layer assignments) |
| `ready`         | `boolean`                        | `true` once the bundle has loaded at least once      |
| `error`         | `Error \| null`                  | Any non-fatal error from the last refresh            |
| `track`         | `(event, options?) => void`      | Track an event                                       |
| `trackExposure` | `() => void`                     | Manually emit an exposure (for `"decision"` mode)    |
| `flushEvents`   | `() => Promise<void>`            | Force flush of pending events                        |

### Tracking modes

```tsx theme={null}
useTraffical({ defaults, tracking: "full" });      // default — automatic decision + exposure
useTraffical({ defaults, tracking: "decision" });  // automatic decision; you call trackExposure()
useTraffical({ defaults, tracking: "none" });      // no tracking (SSR, tests)
```

`"decision"` is useful when the variant is below the fold — you want to record the decision but only count exposure once the user actually sees the change.

## Setting context

Pass user context through the provider; it flows to all hooks:

```tsx theme={null}
<TrafficalProvider
  config={{
    orgId: "org_acme",
    projectId: "proj_marketplace",
    env: "production",
    apiKey: process.env.NEXT_PUBLIC_TRAFFICAL_API_KEY!,
    contextFn: () => ({
      userId: currentUser.id,
      locale: navigator.language,
      plan: currentUser.plan,
    }),
  }}
>
  <MyApp />
</TrafficalProvider>
```

Context is supplied via `contextFn` inside `config` — a function called on each resolution. When the values it returns change (e.g. after login), hooks re-resolve with the new values. To customize the unit key, pass `unitKeyFn` inside `config` as well.

## Anonymous users and `identify`

The React SDK inherits the browser client's stable-ID handling. Before the user logs in, the SDK uses an auto-generated stable ID. After login:

```tsx theme={null}
function Login() {
  const { client } = useTrafficalClient();

  function handleLogin(user) {
    client.identify(user.id);
    // assignments may change for this session
  }
}
```

## SSR (Next.js, RSC)

For Next.js (App Router or Pages), fetch the bundle on the server and pass it to `TrafficalProvider` as `localConfig`. There is no `@traffical/react/server` entry point — fetch the config bundle directly from the SDK config endpoint (the same one the client SDK calls):

```tsx theme={null}
// app/layout.tsx (RSC)
import { TrafficalProvider } from "@traffical/react";

async function fetchBundle() {
  const res = await fetch(
    "https://sdk.traffical.io/v1/config/proj_marketplace?env=production",
    {
      headers: { Authorization: `Bearer ${process.env.TRAFFICAL_API_KEY!}` },
      next: { revalidate: 60 },   // cache via Next.js fetch integration
    },
  );
  return res.ok ? res.json() : null;
}

export default async function RootLayout({ children }) {
  const bundle = await fetchBundle();

  return (
    <TrafficalProvider config={{
      orgId: "org_acme",
      projectId: "proj_marketplace",
      env: "production",
      apiKey: process.env.NEXT_PUBLIC_TRAFFICAL_API_KEY!,
      localConfig: bundle,   // server + client resolve from the same bundle → no flash
    }}>
      {children}
    </TrafficalProvider>
  );
}
```

The same `traffical_sk_...` SDK key is browser-safe, so there is no separate server secret for the SDK path. See the [SSR patterns](/sdks/ssr) page for the full setup, including the Pages Router.

## Standalone tracking

If you only need to track an event, use `useTrafficalTrack`:

```tsx theme={null}
import { useTrafficalTrack } from "@traffical/react";

function Footer() {
  const track = useTrafficalTrack();
  return <a href="#" onClick={() => track("newsletter_signup_clicked")}>Subscribe</a>;
}
```

## Direct client access

If you need to do something the hooks don't expose, get the underlying client. `useTrafficalClient()` returns `{ client, ready, error }` — the client can be `null` until the provider has initialized, so guard it:

```tsx theme={null}
import { useTrafficalClient } from "@traffical/react";

function DebugPanel() {
  const { client, ready } = useTrafficalClient();
  return (
    <button disabled={!ready || !client} onClick={() => client?.flushEvents()}>
      Flush
    </button>
  );
}
```

## Provider options

`TrafficalProvider` takes exactly two props: `config` and `children`. Everything else is a field of `config` (`TrafficalProviderConfig`):

| `config` field      | Type            | Default                    | Description                                                                |
| ------------------- | --------------- | -------------------------- | -------------------------------------------------------------------------- |
| `orgId`             | `string`        | required                   | Organization ID                                                            |
| `projectId`         | `string`        | required                   | Project ID                                                                 |
| `env`               | `string`        | required                   | Environment name                                                           |
| `apiKey`            | `string`        | required                   | SDK key (`traffical_sk_...`, scopes `sdk:read`+`sdk:write`) — browser-safe |
| `baseUrl`           | `string`        | `https://sdk.traffical.io` | SDK API base URL                                                           |
| `localConfig`       | `ConfigBundle`  | —                          | Embedded bundle for cold-start / SSR hydration                             |
| `refreshIntervalMs` | `number`        | `60000`                    | Bundle refresh interval                                                    |
| `unitKeyFn`         | `() => string`  | —                          | Custom unit-key resolution                                                 |
| `contextFn`         | `() => Context` | —                          | Dynamic context — called on every resolution                               |
| `trackDecisions`    | `boolean`       | `true`                     | Emit decision events alongside exposures                                   |
| `plugins`           | `Plugin[]`      | —                          | SDK plugins                                                                |

These are the same options as the [browser SDK](/sdks/javascript#options); pass them all inside `config`.

## Next steps

<CardGroup cols={2}>
  <Card title="SSR patterns" icon="server" href="/sdks/ssr">
    No FOOC, hydration with the same bundle.
  </Card>

  <Card title="Canonical experiments" icon="book-open" href="/guides/canonical-experiments">
    Patterns for web UI and SSR tests.
  </Card>
</CardGroup>
