# React client integration

Last reviewed: 2026-10-16. Targets published `@basestack/flags-react@1.2.4`,
which depends on flags-js 1.2.3 and flags-wc ^1.0.4. Its published peers are
`react >=19.2.7` and `react-dom >=18.2.0`. Do not infer React 18 compatibility
from an older README or a workspace manifest sharing this version number.
Check the consumer's React/React DOM pairing before installation.

```sh
bun add @basestack/flags-react@1.2.4
```

## Provider and safe gate

Use `/client` for hooks, provider, and components. Reuse an existing provider
rather than nesting duplicate clients. This complete client module takes real
keys as props so it works with Next.js public configuration or Vite configuration.
The stable memoized config prevents a client rebuild on every render.

```tsx
"use client";

import { FlagsProvider, useFlag } from "@basestack/flags-react/client";
import { useMemo, type ReactNode } from "react";

export function AppFlags({
  projectKey,
  environmentKey,
  children,
}: {
  projectKey: string;
  environmentKey: string;
  children: ReactNode;
}) {
  const config = useMemo(
    () => ({
      projectKey,
      environmentKey,
      baseURL: "https://flags-api.basestack.co/v1",
      cache: { enabled: false },
    }),
    [projectKey, environmentKey],
  );
  return <FlagsProvider config={config}>{children}</FlagsProvider>;
}

export function CheckoutGate() {
  const result = useFlag<unknown>("new-checkout", { defaultEnabled: false });
  if (result.isLoading) return <p role="status">Loading checkout…</p>;
  if (result.error || result.flag?.error || !result.enabled) {
    return <p>Standard checkout</p>;
  }
  const payload = result.payload;
  const label =
    typeof payload === "object" &&
    payload !== null &&
    "label" in payload &&
    typeof payload.label === "string"
      ? payload.label
      : "New checkout";
  return <p>{label}</p>;
}
```

Mount `CheckoutGate` inside `AppFlags` at the app's existing root/provider
boundary. Disabled and unavailable paths intentionally render the established UI.
The example disables SDK cache to make explicit refetches meaningful; it does
not bypass upstream caching. Configure a TTL if application needs favor caching.

## Hook and component contracts

- `useFlag<T>(slug, { defaultEnabled, defaultPayload, fetch })` returns `flag`,
  `enabled`, `payload`, `isLoading`, `error`, `refresh`, and `openFeedbackModal`.
  A generic only asserts a payload type; it does not validate received JSON.
- HTTP error values may not populate the hook's `error`; also inspect `flag.error`
  and keep disabled defaults. Network/parse failures use the error path.
- `useFlags()` returns `flags` (array), `flagsBySlug` (record), `isLoading`,
  `error`, and `refresh`. Do not destructure it as a plain array.
- `Feature` accepts `slug`, `fallback`, `loading`, and the hook options. With
  ordinary children it chooses the enabled/disabled branch. For explicit error
  checks use a render-function child, which is called before its built-in loading
  branch, and handle loading inside that function:

```tsx
import { Feature } from "@basestack/flags-react/client";

export function CheckoutFeature() {
  return (
    <Feature slug="new-checkout" defaultEnabled={false}>
      {(result) =>
        result.isLoading ? (
          <p role="status">Loading checkout…</p>
        ) : result.enabled && !result.error && !result.flag?.error ? (
          <p>New checkout</p>
        ) : (
          <p>Standard checkout</p>
        )
      }
    </Feature>
  );
}
```

`FlagsProvider` defaults to preloading all flags. `config.preloadFlags` narrows
that list; `preload={false}` skips preload. Nonempty `initialFlags` seeds the
provider and skips its initial fetch; an empty list is not a completed snapshot.

## Refresh, security, and tests

`useFlag().refresh()` refetches through the underlying SDK; it does not clear its
cache. `useFlagsClient()` exposes `clearCache()`/`clearFlagCache(slug)` before an
explicit refresh. `useFlags().refresh()` can reject, so catch it in UI handlers.
No automatic streaming or polling is promised by this provider.

Local preview state (`bs-flags-preview-state`) can make `enabled` true. This is
user-controlled UI behavior, never authorization. Browser keys and payloads are
visible to visitors; private write/API/MCP tokens never belong in provider config.

Test loading, enabled, disabled, absent slug, HTTP error value, rejected fetch,
malformed payload, refresh, and hydration. See
[server frameworks](server-frameworks.md) for server imports and initial data.

Human docs: [React](https://docs.basestack.co/content/feature-flags/sdks/frontend/react).
