> ## 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.

# React

> Bind typed hooks once, wrap your app in ShipdProvider, and read flags in components.

## Overview

In this guide you add typed hooks to a React app: bind them once with `createShipdReact`, provide a browser client, identify the signed-in user, and read flags in components. Hooks re-render when a new snapshot arrives or the identity changes.

This page builds on [Browser](/sdks/browser). For the App Router, also read [Next.js](/sdks/nextjs).

## Bind hooks once

Call `createShipdReact` in one client-only module and import hooks from there. Hooks from a different `createShipdReact` call can't read this provider.

```ts src/lib/shipd-react.ts theme={null}
import { createShipdReact } from "shipdit/client/react";
import type { FlagDefinitions } from "../generated/flags";

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

Keep this out of `src/generated/flags.ts`. Generated files contain types only, so server code can import `FlagDefinitions` without pulling in React.

## Create the client and provider

```tsx src/main.tsx theme={null}
import { createRoot } from "react-dom/client";
import { createClient } from "shipdit/client";
import type { FlagDefinitions } from "./generated/flags";
import { ShipdProvider } from "./lib/shipd-react";
import { App } from "./App";

const client = createClient<FlagDefinitions>({
  sdkKey: import.meta.env.VITE_SHIPD_SDK_KEY, // shipd_sdk_client_…
  stream: true,
  defaults: {
    "new-checkout": false,
    "search-variant": "control",
    "max-retries": 3,
    "checkout-payload": { mode: "strict", showPromo: true },
  },
});

createRoot(document.getElementById("root")!).render(
  <ShipdProvider client={client}>
    <App />
  </ShipdProvider>,
);
```

Create the client once, outside components. Creating it during render starts a new client on every render.

## Identify from your auth layer

```tsx src/components/IdentifyUser.tsx theme={null}
import { useEffect } from "react";
import { useShipdClient } from "../lib/shipd-react";
import { useSession } from "../auth";

export function IdentifyUser() {
  const client = useShipdClient();
  const session = useSession();

  useEffect(() => {
    if (session?.user) {
      client.identify({
        userId: session.user.id,
        traits: { plan: session.user.plan },
      });
    } else {
      client.reset();
    }
  }, [client, session?.user]);

  return null;
}
```

Render `<IdentifyUser />` inside `ShipdProvider`. Every hook re-renders with the new identity. See [Identify users](/flags/identify-users).

## Read flags

```tsx src/components/CheckoutNav.tsx theme={null}
import { useFlag, useReady, useVariant } from "../lib/shipd-react";

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

  if (!ready) return <NavSkeleton />;

  return (
    <nav>
      {newCheckout ? <a href="/checkout/new">Checkout</a> : <a href="/checkout">Checkout</a>}
      {search === "semantic" ? <SemanticSearch /> : <ClassicSearch />}
    </nav>
  );
}
```

Keys are type-checked against the flag type. `useFlag("search-variant")` and `useVariant("new-checkout")` are compile errors.

| Hook | Returns | Client method |
| - | - | - |
| `useFlag(key, context?)` | `boolean` | [`isEnabled`](/reference/sdk#isenabled) |
| `useVariant(key, context?)` | Variant union | [`getVariant`](/reference/sdk#getvariant) |
| `useNumber(key, context?)` | `number` | [`getNumber`](/reference/sdk#getnumber) |
| `useJson(key, context?)` | JSON value | [`getJson`](/reference/sdk#getjson) |
| `useReady()` | `true` once `ready()` settles | [`ready`](/reference/sdk#ready) |
| `useShipdClient()` | The provided client | — |

All hooks throw if they're called outside `ShipdProvider`.

## Handle the loading state

Before the first snapshot loads, hooks return `defaults`. `useReady()` is `false` until the snapshot or cached snapshot loads, or `readyTimeoutMs` elapses. Use it to show a skeleton instead of briefly rendering the default path.

If a default is an acceptable first render (for example, `false` hides an unreleased feature), you can skip `useReady()`. The component re-renders when the snapshot arrives.

## Next

* [Next.js](/sdks/nextjs)
* [Test flags locally and in unit tests](/sdks/testing)
* [Complete React example](/examples/react)
* [SDK reference](/reference/sdk)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.