Skip to main content
@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

npm install @traffical/react

Setup

Wrap your app with TrafficalProvider:
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:
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

OptionTypeDescription
defaultsRecord<string, ParameterValue>Default values for the parameters this component reads. Required.
contextContextOptional extra context merged with provider context.
tracking"full" | "decision" | "none"Tracking mode (see below). Default "full".

Hook return value

FieldTypeDescription
paramsRecord<string, ParameterValue>Resolved parameter values
decisionDecisionResult | nullFull decision record (decisionId, layer assignments)
readybooleantrue once the bundle has loaded at least once
errorError | nullAny non-fatal error from the last refresh
track(event, options?) => voidTrack an event
trackExposure() => voidManually emit an exposure (for "decision" mode)
flushEvents() => Promise<void>Force flush of pending events

Tracking modes

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:
<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:
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 as localConfig:
// app/layout.tsx (RSC)
import { fetchBundle } from "@traffical/react/server";
import { TrafficalProvider } from "@traffical/react";

export default async function RootLayout({ children }) {
  const bundle = await fetchBundle({
    orgId: "org_acme",
    projectId: "proj_marketplace",
    env: "production",
    apiKey: process.env.TRAFFICAL_API_KEY!,    // server-only
  });

  return (
    <TrafficalProvider config={{
      orgId: "org_acme",
      projectId: "proj_marketplace",
      env: "production",
      apiKey: process.env.NEXT_PUBLIC_TRAFFICAL_API_KEY!,    // public
      localConfig: bundle,
    }}>
      {children}
    </TrafficalProvider>
  );
}
See the SSR patterns page for the full setup.

Standalone tracking

If you only need to track an event, use useTrafficalTrack:
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:
import { useTrafficalClient } from "@traffical/react";

function DebugPanel() {
  const client = useTrafficalClient();
  return <button onClick={() => client.flushEvents()}>Flush</button>;
}

Provider options

TrafficalProvider takes exactly two props: config and children. Everything else is a field of config (TrafficalProviderConfig):
config fieldTypeDefaultDescription
orgIdstringrequiredOrganization ID
projectIdstringrequiredProject ID
envstringrequiredEnvironment name
apiKeystringrequiredSDK key (traffical_sk_..., scopes sdk:read+sdk:write) — browser-safe
baseUrlstringhttps://sdk.traffical.ioSDK API base URL
localConfigConfigBundleEmbedded bundle for cold-start / SSR hydration
refreshIntervalMsnumber60000Bundle refresh interval
unitKeyFn() => stringCustom unit-key resolution
contextFn() => ContextDynamic context — called on every resolution
trackDecisionsbooleantrueEmit decision events alongside exposures
pluginsPlugin[]SDK plugins
These are the same options as the browser SDK; pass them all inside config.

Next steps

SSR patterns

No FOOC, hydration with the same bundle.

Canonical experiments

Patterns for web UI and SSR tests.