> ## 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 Native SDK

> Feature flags for React Native with offline cache and foreground refresh

## Installation

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

Follow the [AsyncStorage native setup](https://github.com/react-native-async-storage/async-storage#installation) to link the native module.

**Peer dependencies:** React Native 0.68+, `@react-native-async-storage/async-storage` 1.0+

## Setup

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

export default function App() {
  return (
    <OmniFlagsProvider sdkKey="pk_live_...">
      <RootNavigator />
    </OmniFlagsProvider>
  );
}
```

Flags are available immediately on launch from the local cache, with a background refresh running in parallel. This applies on subsequent launches even when the device is offline.

### First launch

No cache exists on a fresh install, so flags start loading. Gate on `isLoading` from `useFlagStatus` if your app needs flags before it can render:

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

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

After the first snapshot is cached this won't trigger again, even offline.

***

## Hooks

Same hooks as the React SDK, imported from `@omniretail/omniflags-react-native`.

### `useFlag`

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

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

function CheckoutScreen({ customerId }: { customerId: string }) {
  const chargeDeliveryFee = useFlag('checkout.charge-delivery-fee', { customerId });
  return (
    <View>
      <Text>{chargeDeliveryFee ? 'Delivery fee applies' : 'Free delivery'}</Text>
    </View>
  );
}
```

| 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-native';

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

```tsx theme={null}
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).

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

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,
    });
  }, [result.variant]);

  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-native';

function DebugOverlay() {
  const { isLoading, isFetching, origin, error } = useFlagStatus();
  return (
    <Text style={styles.debug}>
      Flags: {isLoading ? 'loading' : origin} {isFetching ? '(refreshing)' : ''}
    </Text>
  );
}
```

| 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-native';

function AuthenticatedNavigator({ user }: { user: CurrentUser }) {
  useSetFlagContext({
    customerId: user.id,
    country: user.country,
    platform: Platform.OS,
  });

  return <RootNavigator />;
}
```

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: 'android',
  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>

***

## Offline support

The last successful snapshot is persisted locally and loaded on the next launch before any network activity.

* `origin` is `'CACHE'` while a background fetch is pending, `'SERVER'` once it completes.
* On fetch failure `origin` stays `'CACHE'` and flags keep serving from the cached snapshot.

## Foreground refresh

A snapshot refresh is triggered automatically when the app returns to the foreground. Flag changes made in the dashboard reach active users without requiring an app restart.
