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

# Use shipd in React

> Bind typed hooks once, wrap the tree, and read flags in components.

## Overview

Jordan adds the new checkout nav to Fieldkit's React app. Hooks are bound **once** in a client-only module so server code can import `FlagDefinitions` without pulling React.

shipd's own dashboard does this via (`useFlag("audit-log")`, `useVariant("rules-editor")`).

## Bind hooks

```ts src/lib/shipd.ts theme={null}
"use client";

import { createShipdReact } from "shipdit/client/react";
import type { FlagDefinitions } from "./generated/flags";

export const {
  ShipdProvider,
  useFlag,
  useVariant,
  useNumber,
  useJson,
  useReady,
  useShipdClient,
} = createShipdReact<FlagDefinitions>();
```

Do not put `createShipdReact` in typegen output. `shipd pull` / `types gen` emit types only.

## Create the client and wrap

```tsx src/app/providers.tsx theme={null}
"use client";

import { createClient } from "shipdit/client";
import type { FlagDefinitions } from "@/lib/generated/flags";
import { ShipdProvider } from "@/lib/shipd";

const client = createClient<FlagDefinitions>({
  sdkKey: process.env.NEXT_PUBLIC_SHIPD_SDK_KEY!,
  endpoint: "https://edge.useshipd.com",
  streamEndpoint: "https://ws.useshipd.com",
  defaults: {
    "new-checkout": false,
    "search-variant": "control",
  },
});

export function Providers({ children }: { children: React.ReactNode }) {
  return <ShipdProvider client={client}>{children}</ShipdProvider>;
}
```

Identify from your auth layer — hooks subscribe through `client.subscribe`:

```ts theme={null}
client.identify({
  userId: session.user.id,
  traits: { plan: session.plan },
});
```

## Read flags

```tsx src/components/CheckoutNav.tsx theme={null}
"use client";

import { useFlag, useReady, useVariant } from "@/lib/shipd";

export function CheckoutNav() {
  const ready = useReady();
  const newCheckout = useFlag("new-checkout");
  const search = useVariant("search-variant");

  if (!ready) return null;

  return (
    <nav>
      {newCheckout ? <NewCheckoutLink /> : <LegacyCheckoutLink />}
      {search === "semantic" ? <SemanticSearch /> : <ClassicSearch />}
    </nav>
  );
}
```

`useFlag("search-variant")` does not type-check — that key is a variant. `useVariant("new-checkout")` does not type-check either.

| Hook                        | Client method         |
| --------------------------- | --------------------- |
| `useFlag(key, context?)`    | `isEnabled`           |
| `useVariant(key, context?)` | `getVariant`          |
| `useNumber(key, context?)`  | `getNumber`           |
| `useJson(key, context?)`    | `getJson`             |
| `useReady()`                | `ready()` has settled |
| `useShipdClient()`          | the provided client   |

Hooks re-render when a new snapshot applies or when `identify` / `reset` changes context.

## Dogfood: the shipd dashboard

```ts apps/web/src/lib/shipd.ts theme={null}
import { createBrowserClient } from "@shipd/flags/client";

export const client = createBrowserClient({
  sdkKey: import.meta.env.VITE_SHIPD_PUBLIC_KEY,
  endpoint: import.meta.env.VITE_SHIPD_EDGE_URL,
  streamEndpoint: import.meta.env.VITE_SHIPD_WS_URL,
});
```

```tsx theme={null}
const auditLogEnabled = useFlag("audit-log", {
  userId: user.id,
  attributes: { plan, role },
});
const ruleUiVariant = useVariant("rules-editor");
```

Empty env keys skip the data plane and keep defaults (`rules-editor: "v1"`, `audit-log: false`) so local dashboard boots without hosted KV.

## Next

* How-to: [Use React hooks](/how-to/use-react-hooks)
* [SDK reference](/reference/sdk)
