> ## 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 (Vite) example

> A complete Vite + React single-page app that reads shipd flags with typed hooks and identifies users after sign-in.

## Overview

A complete Vite + React single-page app. It creates one browser client with a client SDK key, reads `new-checkout` and `search-variant` with typed hooks, and calls `identify` after sign-in.

## What's in it

* `src/lib/shipd.ts` creates the browser client from `import.meta.env.VITE_SHIPD_SDK_KEY` and binds typed hooks with `createShipdReact<FlagDefinitions>()`.
* `src/main.tsx` wraps the app in `ShipdProvider`.
* `src/App.tsx` signs a user in and out. It calls `identify` on sign-in and `reset` on sign-out, and the hooks re-render.
* The browser client caches the last good snapshot and a stable anonymous id in `localStorage`, so repeat visits render flags before the network responds.

```text theme={null}
react-example/
├── .env.example
├── index.html
├── package.json
├── shipd.config.ts
├── tsconfig.json
├── vite.config.ts
└── src/
    ├── App.tsx
    ├── main.tsx
    ├── vite-env.d.ts
    ├── components/
    │   ├── checkout-button.tsx
    │   └── search-box.tsx
    ├── generated/
    │   └── flags.ts
    └── lib/
        └── shipd.ts
```

## Files

```json package.json theme={null}
{
  "name": "react-example",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc --noEmit && vite build",
    "preview": "vite preview",
    "flags:diff": "shipd diff",
    "flags:push": "shipd push"
  },
  "dependencies": {
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "shipdit": "^0.4.0"
  },
  "devDependencies": {
    "@shipdit/cli": "^0.3.2",
    "@types/react": "^19.2.0",
    "@types/react-dom": "^19.2.0",
    "@vitejs/plugin-react": "^5.0.0",
    "typescript": "^5.9.0",
    "vite": "^7.1.0"
  }
}
```

```json tsconfig.json theme={null}
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "strict": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src", "shipd.config.ts", "vite.config.ts"]
}
```

```ts vite.config.ts theme={null}
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [react()],
});
```

```html index.html theme={null}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>shipd React example</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
```

```ts shipd.config.ts theme={null}
import { defineFlags } from "shipdit/config";

export default defineFlags({
  "new-checkout": {
    type: "boolean",
    default: false,
    description: "Redesigned checkout flow",
    tags: ["checkout"],
  },
  "search-variant": {
    type: "variant",
    variants: ["control", "semantic", "hybrid"] as const,
    default: "control",
    description: "Search ranking experiment",
    tags: ["search", "experiment"],
  },
});
```

```bash .env.example theme={null}
# Client SDK key (shipd_sdk_client_…). Vite inlines VITE_* values into the bundle.
# Never put a server key here.
VITE_SHIPD_SDK_KEY=shipd_sdk_client_...
```

```ts src/vite-env.d.ts theme={null}
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_SHIPD_SDK_KEY: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}
```

`shipd push` and `shipd types gen` write this file. Commit it and do not edit it by hand.

```ts src/generated/flags.ts theme={null}
/* eslint-disable */
/* This file is generated by shipd types gen. Do not edit. */

export interface FlagDefinitions {
  "new-checkout": {
    type: "boolean";
    default: false;
  };

  "search-variant": {
    type: "variant";
    variants: "control" | "semantic" | "hybrid";
    default: "control";
  };
}

export type FlagKey = keyof FlagDefinitions;

export type BooleanFlagKey = {
  [K in FlagKey]: FlagDefinitions[K]["type"] extends "boolean" ? K : never;
}[FlagKey];

export type VariantFlagKey = {
  [K in FlagKey]: FlagDefinitions[K]["type"] extends "variant" ? K : never;
}[FlagKey];

export type NumberFlagKey = {
  [K in FlagKey]: FlagDefinitions[K]["type"] extends "number" ? K : never;
}[FlagKey];

export type JsonFlagKey = {
  [K in FlagKey]: FlagDefinitions[K]["type"] extends "json" ? K : never;
}[FlagKey];
```

```ts src/lib/shipd.ts theme={null}
import { createClient } from "shipdit/client";
import { createShipdReact } from "shipdit/client/react";

import type { FlagDefinitions } from "../generated/flags";

// One client for the whole app. Create it at module scope, not inside a component.
export const shipd = createClient<FlagDefinitions>({
  sdkKey: import.meta.env.VITE_SHIPD_SDK_KEY,
  stream: true,
  defaults: {
    "new-checkout": false,
    "search-variant": "control",
  },
});

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

```tsx src/main.tsx theme={null}
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import { App } from "./App";
import { ShipdProvider, shipd } from "./lib/shipd";

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

Replace `signIn` with your auth library. The important part is calling `identify` with the user's id and traits once you know them.

```tsx src/App.tsx theme={null}
import { useState } from "react";

import { CheckoutButton } from "./components/checkout-button";
import { SearchBox } from "./components/search-box";
import { useShipdClient } from "./lib/shipd";

type User = { id: string; name: string; plan: "free" | "pro" };

async function signIn(): Promise<User> {
  return { id: "usr_8f2k19", name: "Sam Lee", plan: "pro" };
}

export function App() {
  const shipd = useShipdClient();
  const [user, setUser] = useState<User | null>(null);

  async function handleSignIn() {
    const next = await signIn();
    // Every later evaluation uses this userId and these traits.
    shipd.identify({ userId: next.id, name: next.name, traits: { plan: next.plan } });
    setUser(next);
  }

  function handleSignOut() {
    // Clears the identity and starts a new anonymous id.
    shipd.reset();
    setUser(null);
  }

  return (
    <main>
      <header>
        {user ? (
          <>
            <span>
              {user.name} ({user.plan})
            </span>
            <button onClick={handleSignOut}>Sign out</button>
          </>
        ) : (
          <button onClick={handleSignIn}>Sign in</button>
        )}
      </header>
      <SearchBox />
      <CheckoutButton />
    </main>
  );
}
```

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

export function CheckoutButton() {
  const ready = useReady();
  const newCheckout = useFlag("new-checkout");

  // Avoid flashing the old checkout before the first snapshot arrives.
  if (!ready) return null;

  return newCheckout ? (
    <a href="/checkout/v2">Try the new checkout</a>
  ) : (
    <a href="/checkout">Checkout</a>
  );
}
```

```tsx src/components/search-box.tsx theme={null}
import { useVariant } from "../lib/shipd";

const placeholders = {
  control: "Search products",
  semantic: "Describe what you're looking for",
  hybrid: "Search by name or description",
} as const;

export function SearchBox() {
  // "control" | "semantic" | "hybrid"
  const variant = useVariant("search-variant");

  return (
    <form action="/search" data-search-variant={variant}>
      <input type="search" name="q" placeholder={placeholders[variant]} />
    </form>
  );
}
```

## Run it

```bash theme={null}
pnpm install
cp .env.example .env        # paste your client SDK key
pnpm exec shipd login       # approve org → project → environment in the browser
pnpm exec shipd push        # creates the flags and rewrites src/generated/flags.ts
pnpm dev
```

Open `http://localhost:5173`. Pushed flags serve their `default`. In the dashboard, add a rule to `new-checkout` that serves `true` when `plan` equals `pro`, then click **Save environment**. Click **Sign in** and the checkout link switches without a reload.

## Adapt it

* Call `identify` wherever your auth state settles (session restore, sign-in callback). Call `reset` on sign-out.
* Pass per-component context only for values that aren't about the user, such as `useFlag("new-checkout", { attributes: { cartSize: 3 } })`. Per-call attributes override `identify` traits.
* The browser client adds `country`, `region`, `city`, and `timezone` attributes automatically. Pass `automaticTraits: false` to turn that off.
* Gate UI with `useReady()` only where a flash of the default matters. The `localStorage` cache makes repeat visits ready almost immediately.

## Next

* [React SDK guide](/sdks/react)
* [SDK keys](/concepts/keys)
* [SDK reference](/reference/sdk)
* [Generated types](/config-as-code/types)


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