npm package: @featureflare/react
Description: React hooks and provider for FeatureFlare feature flags.
Latest version: 0.0.143

# @featureflare/react

React hooks and provider for FeatureFlare feature flags.

## Installation

```bash
npm install @featureflare/react
# or
pnpm add @featureflare/react
# or
yarn add @featureflare/react
```

## Usage

### Minimal Setup

```typescript
import { FeatureFlareProvider, resolveFeatureFlareBrowserConfig, useFlags } from '@featureflare/react';

function FlagsBootstrap() {
  useFlags({
    user: { id: 'user-123', email: 'dev@company.com' },
    defaultValue: false,
    refreshIntervalMs: 10000,
    pauseWhenHidden: true,
  });

  return null;
}

export function App() {
  const config = resolveFeatureFlareBrowserConfig();

  if (!config.sdkKey) {
    return <div>Feature flags disabled</div>;
  }

  return (
    <FeatureFlareProvider
      config={{
        ...config,
        bootstrap: {
          flags: { 'new-nav': true }
        }
      }}
      initialUser={{ id: 'user-123', key: 'user-123' }}
    >
      <FlagsBootstrap />
      {/* app */}
    </FeatureFlareProvider>
  );
}
```

### Read One Flag

```typescript
import { useFlag } from '@featureflare/react';

export function NewNav() {
  const { value, loading, error } = useFlag('new-nav', false);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return value ? <div>New nav ON</div> : <div>New nav OFF</div>;
}
```

### SSR/HTML Bootstrap (anti-flicker working flow)

Server-side (example in Next.js `getServerSideProps`):

```typescript
import { FeatureFlareClient } from '@featureflare/sdk-js';

export async function getServerSideProps() {
  const user = { id: 'user-123', key: 'user-123' };
  const sdk = new FeatureFlareClient({
    apiBaseUrl: process.env.FEATUREFLARE_API_BASE_URL,
    sdkKey: process.env.FEATUREFLARE_SERVER_SDK_KEY
  });

  const nav = await sdk.bool('new-nav', user, false);

  return {
    props: {
      user,
      ffBootstrap: {
        flags: { 'new-nav': nav }
      }
    }
  };
}
```

Client-side provider wiring:

```typescript
import { FeatureFlareProvider, resolveFeatureFlareBrowserConfig } from '@featureflare/react';

export default function Page({ user, ffBootstrap }) {
  const config = resolveFeatureFlareBrowserConfig();

  return (
    <FeatureFlareProvider
      config={{
        ...config,
        bootstrap: ffBootstrap
      }}
      initialUser={user}
    >
      {/* First render uses bootstrap synchronously; no loading flicker for these flags */}
      <App />
    </FeatureFlareProvider>
  );
}
```

For non-SSR apps, inject a bootstrap JSON payload into HTML before app startup and pass it to `config.bootstrap` the same way.

## API

### `FeatureFlareProvider`

Props:

- `config: FeatureFlareReactConfig`
- `initialUser: FeatureFlareUserPayload`
- `user?: FeatureFlareUserPayload` (controlled mode)
- `onUserChange?: (user: FeatureFlareUserPayload) => void` (required in controlled mode)
- `children: React.ReactNode`

Config notes:

- `config.waitForFlags?: boolean` blocks the first render until the provider loads the initial flag snapshot.
- `config.waitForFlagAuth?: boolean` is a deprecated alias for `waitForFlags`.
- If bootstrap or persistent cache data already exists, the provider does not wait.

### `resolveFeatureFlareBrowserConfig(input?)`

Builds provider config from common `NEXT_PUBLIC_FEATUREFLARE_*` vars.

Returns:

- `apiBaseUrl?: string`
- `envKey: 'development' | 'staging' | 'production'`
- `sdkKey?: string`
- `waitForFlags?: boolean`

### `useFeatureFlareUser()`

Returns:

- `[user, setUser]`

### `useFlags(...)`

Signatures:

- `useFlags(keys: string[], defaultValue?: boolean)` → `{ values, loading, errors }`
- `useFlags(input?: { user?: FeatureFlareUserPayload; defaultValue?: boolean; refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })`
- `useFlags(defaultValue?: boolean, options?: { refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })`

### `useBoolFlags(...)`

Signatures:

- `useBoolFlags(keys: string[], defaultValue?: boolean)` → `{ values, loading, errors }`
- `useBoolFlags(defaultValue?: boolean, options?: { refreshIntervalMs?: number; hiddenRefreshIntervalMs?: number; pauseWhenHidden?: boolean; enabled?: boolean })` → `{ values, loading, errors }` (all flags)

Returns:

- `flags: Array<{ key: string; value: boolean }>`
- `loading: boolean`
- `error: string | null`

### `useFlag(key, defaultValue?)`

Selector-based single-flag subscription. Components only re-render when that flag changes.

### `useFlagDiagnostics(key)`

Returns per-flag runtime diagnostics (source, stale state, cache timestamps, latency).

Behavior:

- Immediate fetch on mount/user change.
- Shared polling via provider context (multiple consumers do not duplicate requests).
- Optional polling controls with hidden-tab awareness.
- If `user` is provided in input form, hook syncs provider user automatically.

## Notes

- Use client SDK keys in browser apps.
- If `config.sdkKey` is missing, skip initializing the provider.
- Leave `waitForFlags` off to keep the current async hook behavior and per-hook `loading` states.

## Security: Client-side flags are not authorization

Client keys can be extracted from your frontend bundle. **Never gate truly sensitive operations solely with client-evaluated flags**—enforce authorization on your backend.

- `config.bootstrap?: { flags, killSwitches }` for SSR/HTML bootstrap to avoid first-render flicker
- `config.timeoutMs | maxRetries | backoffMs | jitter | cacheTtlMs | staleTtlMs | realtime` are forwarded to the SDK client