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

# React SDK

> Feature flags for React 18+ web applications

## Installation

```bash theme={null}
npm install @omniretail/omniflags-react
```

**Peer dependencies:** React 18+

## Setup

Wrap your app root with `OmniFlagsProvider`. It fetches the flag snapshot and starts background polling.

<Tabs>
  <Tab title="Vite / CRA">
    ```tsx theme={null}
    // main.tsx
    import { createRoot } from 'react-dom/client';
    import { OmniFlagsProvider } from '@omniretail/omniflags-react';
    import App from './App';

    createRoot(document.getElementById('root')!).render(
      <OmniFlagsProvider sdkKey="pk_live_...">
        <App />
      </OmniFlagsProvider>
    );
    ```
  </Tab>

  <Tab title="Next.js (App Router)">
    Import `OmniFlagsProvider` from the `/nextjs` entry point and use it
    directly in your root layout.

    ```tsx theme={null}
    // app/layout.tsx
    import { OmniFlagsProvider } from '@omniretail/omniflags-react/nextjs';

    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <OmniFlagsProvider sdkKey={process.env.NEXT_PUBLIC_OMNIFLAGS_SDK_KEY!}>
              {children}
            </OmniFlagsProvider>
          </body>
        </html>
      );
    }
    ```

    Any component that uses flag hooks must be a Client Component:

    ```tsx theme={null}
    // app/components/promo-banner.tsx
    'use client';

    import { useFlagValue } from '@omniretail/omniflags-react';

    export function PromoBanner() {
      const colour = useFlagValue('product-listing.promo-banner-colour', 'blue');
      return <div style={{ background: colour }}>Limited time offer</div>;
    }
    ```

    ```tsx theme={null}
    // app/page.tsx
    import { PromoBanner } from './components/promo-banner';

    export default function Page() {
      return (
        <main>
          <PromoBanner />
        </main>
      );
    }
    ```
  </Tab>

  <Tab title="Next.js (Pages Router)">
    ```tsx theme={null}
    // pages/_app.tsx
    import type { AppProps } from 'next/app';
    import { OmniFlagsProvider } from '@omniretail/omniflags-react';

    export default function MyApp({ Component, pageProps }: AppProps) {
      return (
        <OmniFlagsProvider sdkKey={process.env.NEXT_PUBLIC_OMNIFLAGS_SDK_KEY!}>
          <Component {...pageProps} />
        </OmniFlagsProvider>
      );
    }
    ```
  </Tab>
</Tabs>

### Loading state

Hooks return `defaultValue` until the first snapshot arrives. If you need to gate rendering on that:

```tsx theme={null}
import { useFlagStatus } from '@omniretail/omniflags-react';

function AppShell() {
  const { isLoading } = useFlagStatus();
  if (isLoading) return <FullScreenSpinner />;
  return <App />;
}
```

Most apps don't need this. Hooks update in place once the snapshot arrives.

<Note>
  When using `OmniFlagsProvider` (Next.js App Router), flags are ready before
  the first render. `isLoading` will always be `false` and `defaultValue` fallbacks
  are never shown.
</Note>

***

## Hooks

### `useFlag`

Boolean flag evaluation. Re-renders only when this flag's value changes.

```tsx theme={null}
import { useFlag } from '@omniretail/omniflags-react';

function PromotionalBanner({ customerId }: { customerId: string }) {
  const showBanner = useFlag('product-listing.show-promo-products', { customerId });
  if (!showBanner) return null;
  return <Banner />;
}
```

| Parameter      | Type                | Default  | Description                                                    |
| -------------- | ------------------- | -------- | -------------------------------------------------------------- |
| `flagKey`      | `string`            | required | `{projectKey}.{flagKey}`                                       |
| `context`      | `EvaluationContext` | `{}`     | Targeting attributes                                           |
| `defaultValue` | `boolean`           | `false`  | Fallback when flag is missing, wrong type, or client not ready |

***

### `useFlagValue`

Returns the typed value of any flag: `string`, `number`, or `object`.

```tsx theme={null}
import { useFlagValue } from '@omniretail/omniflags-react';

function PromoBanner({ customerId }: { customerId: string }) {
  const colour = useFlagValue('product-listing.promo-banner-colour', 'blue', { customerId });
  return <div style={{ backgroundColor: colour }}>Limited time offer</div>;
}
```

```tsx theme={null}
const maxCartItems = useFlagValue('cart.max-items', 20, { customerId });
const config = useFlagValue<CheckoutConfig>('checkout.config', defaultConfig, { customerId });
```

| Parameter      | Type                | Required | Description              |
| -------------- | ------------------- | -------- | ------------------------ |
| `flagKey`      | `string`            | yes      | `{projectKey}.{flagKey}` |
| `defaultValue` | `T`                 | yes      | Fallback value           |
| `context`      | `EvaluationContext` | no       | Targeting attributes     |

***

### `useFlagVariant`

Returns the full `EvaluationResult` (variant key, reason, and rule ID). Useful when you need to log evaluation details or branch on variant names directly.

```tsx theme={null}
import { useFlagVariant } from '@omniretail/omniflags-react';

function BannerWithAnalytics({ customerId }: { customerId: string }) {
  const result = useFlagVariant('product-listing.promo-banner-colour', { customerId });

  useEffect(() => {
    analytics.track('flag_evaluated', {
      flag: 'product-listing.promo-banner-colour',
      variant: result.variant,
      reason: result.reason,
      ruleId: result.ruleId,
    });
  }, [result.variant, result.reason]);

  return <Banner colour={result.value as string} />;
}
```

| Field       | Type                | Description                                           |
| ----------- | ------------------- | ----------------------------------------------------- |
| `value`     | `unknown`           | Resolved flag value                                   |
| `variant`   | `string \| null`    | Matched variant key                                   |
| `reason`    | `EvaluationReason`  | See [reason codes](/concepts/evaluation#reason-codes) |
| `ruleId`    | `string \| null`    | Matched rule ID, if any                               |
| `errorCode` | `ErrorCode \| null` | Set when `reason` is `ERROR`                          |

***

### `useFlagStatus`

Current state of the flag client.

```tsx theme={null}
import { useFlagStatus } from '@omniretail/omniflags-react';

function DiagnosticBadge() {
  const { isLoading, isFetching, origin, error } = useFlagStatus();
  if (error) console.error('OmniFlags fetch failed:', error);
  return <span title={`Flags: ${origin}`}>{isFetching ? '⟳' : '✓'}</span>;
}
```

| Field        | Type                            | Description                                              |
| ------------ | ------------------------------- | -------------------------------------------------------- |
| `isLoading`  | `boolean`                       | `true` until the first snapshot loads (cache or network) |
| `isFetching` | `boolean`                       | `true` while a network request is in flight              |
| `origin`     | `'NONE' \| 'CACHE' \| 'SERVER'` | Source of the active snapshot                            |
| `error`      | `Error \| null`                 | Last fetch error                                         |

***

### `useSetFlagContext`

Sets a global evaluation context for the whole provider tree. Individual hook calls that pass their own `context` will be merged with it; per-call attributes win on conflict.

```tsx theme={null}
import { useSetFlagContext } from '@omniretail/omniflags-react';

function AuthenticatedApp({ user }: { user: CurrentUser }) {
  useSetFlagContext({
    customerId: user.id,
    country: user.country,
    platform: 'web',
  });

  return <App />;
}
```

Clears on unmount.

| Parameter | Type                             | Description                           |
| --------- | -------------------------------- | ------------------------------------- |
| `context` | `EvaluationContext \| undefined` | Pass `undefined` to clear explicitly. |

***

## Evaluation context

Attributes used to match targeting rules and assign rollout buckets. Pass per-hook or once globally via `useSetFlagContext`.

```tsx theme={null}
useFlag('checkout.charge-delivery-fee', {
  customerId: '4821',
  country: 'Nigeria',
  city: 'Lagos',
  platform: 'web',
  appVersion: '4.1.0',
});
```

| Attribute          | Type               | Notes                            |
| ------------------ | ------------------ | -------------------------------- |
| `customerId`       | `string \| number` | Primary bucketing key            |
| `agentId`          | `string \| number` | Used when `customerId` is absent |
| `businessId`       | `string \| number` |                                  |
| `businessBranchId` | `string \| number` |                                  |
| `country`          | `string`           | e.g. `"Nigeria"`, `"Ghana"`      |
| `city`             | `string`           |                                  |
| `platform`         | `string`           | `web`, `ios`, `android`          |
| `appVersion`       | `string`           | Semver string                    |

Any extra key-value pairs are forwarded to targeting rules as custom attributes.

<Warning>
  Rollout and traffic splits require a bucketing key (`customerId`, `agentId`, etc.). Without one the SDK returns the flag's default value with `MISSING_TARGETING_KEY`.
</Warning>
