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

# React Native SDK

> iOS and Android via React Native — cold-start strategy, AsyncStorage caching, device-info enrichment.

`@traffical/react-native` is the SDK for iOS and Android apps built with React Native. It handles the constraints mobile imposes — cold starts, offline use, app suspension, device context — and ships with sensible defaults so most apps don't have to think about them.

## Why mobile is different

Mobile has constraints web and backend don't:

* **Cold start.** On first launch there's no cached bundle and no network response yet. The user sees the app before any experiment can resolve.
* **Offline use.** Users open the app on planes and in subways. Resolution must work without connectivity.
* **App store latency.** Code changes take days. Parameter changes via Traffical are instant — this is the whole point of having a remote configuration system at all.
* **Background suspension.** The app may be paused for hours and resumed. Cached assignments need refreshing.

The React Native SDK defaults to `evaluationMode: "server"`: each resolution is evaluated for that call's context on the edge, and the response is cached and persisted to `AsyncStorage` across launches. Because `getParams`/`decide` are synchronous, a call returns the last-good cached response and converges as fresh resolves land. You can override with `evaluationMode: "bundle"` if you'd rather embed and evaluate the full bundle locally.

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @traffical/react-native @react-native-async-storage/async-storage
  ```

  ```bash pnpm theme={null}
  pnpm add @traffical/react-native @react-native-async-storage/async-storage
  ```

  ```bash yarn theme={null}
  yarn add @traffical/react-native @react-native-async-storage/async-storage
  ```
</CodeGroup>

For iOS, run `pod install` in the `ios/` directory after adding `@react-native-async-storage/async-storage`.

## Setup

```tsx theme={null}
import { TrafficalRNProvider } from "@traffical/react-native";

function App() {
  return (
    <TrafficalRNProvider config={{
      orgId: "org_acme",
      projectId: "proj_marketplace",
      env: "production",
      apiKey: "traffical_sk_...",
    }}>
      <Navigator />
    </TrafficalRNProvider>
  );
}
```

Use an SDK key (`traffical_sk_...`, scopes `sdk:read`+`sdk:write`) — browser-safe. The SDK auto-registers the AsyncStorage cache and lifecycle hooks for foreground/background transitions.

## Resolving parameters

Same hook as the React SDK:

```tsx theme={null}
import { useTraffical } from "@traffical/react-native";

function Onboarding() {
  const { params } = useTraffical({
    defaults: {
      "mobile.onboarding_steps": 3,
      "mobile.onboarding_style": "carousel",
    },
  });

  return (
    <OnboardingCarousel
      steps={params["mobile.onboarding_steps"]}
      style={params["mobile.onboarding_style"]}
    />
  );
}
```

## Cold start strategy

Resolution checks three sources in order:

| Priority | Source                                           | When available                 | Staleness                |
| -------- | ------------------------------------------------ | ------------------------------ | ------------------------ |
| 1        | Cached assignments (AsyncStorage)                | After first successful resolve | Refreshed per session    |
| 2        | `localConfig` bundle (baked into the app binary) | Always (compiled in)           | As of the last app build |
| 3        | Caller `defaults` from `getParams`               | Always                         | Hardcoded fallback       |

**First launch, no localConfig:** the user sees defaults until the first network call returns. If your first-launch onboarding is a critical experiment, embed a `localConfig` bundle built during CI:

```tsx theme={null}
<TrafficalRNProvider config={{
  orgId: "org_acme",
  projectId: "proj_marketplace",
  env: "production",
  apiKey: "traffical_sk_...",
  localConfig: require("./traffical-bundle.json"),    // generated by CI
}}>
```

**Returning user:** the SDK reads cached assignments from AsyncStorage synchronously before the first render. The user immediately sees their previous-session assignment. A background refresh updates for next time.

**Returning user after long absence (cache expired):** falls back to `localConfig`, then to caller defaults.

## Device-info enrichment

The SDK can enrich `context` with device info — useful for OS-specific experiments and analytics. You supply the data through a `deviceInfoProvider`: any object implementing the `DeviceInfoProvider` interface. The SDK ships the interface (`DeviceInfoProvider` / `DeviceInfo` types) but does **not** ship a built-in implementation, so you provide one — typically backed by `react-native-device-info` plus React Native's `Platform` and `Dimensions`:

```typescript theme={null}
export interface DeviceInfoProvider {
  getDeviceInfo(): DeviceInfo;
}

export interface DeviceInfo {
  appVersion?: string;
  appBuildNumber?: string;
  deviceModel?: string;
  deviceModelName?: string;
  osName?: string;
  osVersion?: string;
  locale?: string;
  timezone?: string;
  screenWidth?: number;
  screenHeight?: number;
  pixelRatio?: number;
}
```

```tsx theme={null}
import { TrafficalRNProvider, type DeviceInfoProvider } from "@traffical/react-native";
import { Platform, Dimensions } from "react-native";
import DeviceInfo from "react-native-device-info";

const deviceInfoProvider: DeviceInfoProvider = {
  getDeviceInfo() {
    const { width, height } = Dimensions.get("window");
    return {
      appVersion: DeviceInfo.getVersion(),
      osName: Platform.OS,          // "ios" | "android"
      osVersion: String(Platform.Version),
      deviceModel: DeviceInfo.getModel(),
      screenWidth: width,
      screenHeight: height,
    };
  },
};

<TrafficalRNProvider config={{ orgId, projectId, env, apiKey, deviceInfoProvider }}>
```

The returned fields merge into `context` on every resolution, so they're available for policy conditions — e.g. `osName eq "ios"` for an iOS-only experiment.

<Warning>
  **Version-string targeting is a known gap.** The numeric comparison operators (`gt`/`gte`/`lt`/`lte`) only compare **numbers** — a condition like `appVersion gte "2.0.0"` won't match, because `"2.0.0"` is a string. To gate on a minimum version today, target an exact set of versions with `in`, or expose a numeric build number (`appBuildNumber`) and compare that. Semver-aware version conditions are not yet supported.
</Warning>

## Tracking events

```tsx theme={null}
import { useTrafficalTrack } from "@traffical/react-native";

function PaywallScreen() {
  const track = useTrafficalTrack();
  return <Button onPress={() => track("paywall_purchase_started")} title="Subscribe" />;
}
```

Events are batched in memory and flushed on background or app close.

## Foreground refresh

When the app returns to the foreground after being suspended, the SDK checks whether the cache is stale and refreshes silently if so. Existing assignments stay stable — the refresh only matters for *new* parameters or *next-session* resolutions.

## Options

| Option                                | Type                   | Default    | Description                                                                   |
| ------------------------------------- | ---------------------- | ---------- | ----------------------------------------------------------------------------- |
| `orgId`, `projectId`, `env`, `apiKey` | —                      | required   | Same as other SDKs                                                            |
| `evaluationMode`                      | `"bundle" \| "server"` | `"server"` | Mobile-friendly default                                                       |
| `localConfig`                         | `ConfigBundle`         | —          | Embedded bundle for cold-start                                                |
| `deviceInfoProvider`                  | `DeviceInfoProvider`   | —          | Device-context enrichment (you supply the implementation)                     |
| `cacheMaxAgeMs`                       | `number`               | `86400000` | Max age of the persisted server response before it's treated as expired (24h) |
| `refreshIntervalMs`                   | `number`               | `60000`    | Background refresh cadence                                                    |

Persistence is backed by `AsyncStorage` automatically — the provider registers it for you, so there's no `storage` option to set. The provider also accepts the common options shared with the other SDKs (`trackDecisions`, `exposureSessionTtlMs`, `eventBatchSize`, `eventFlushIntervalMs`, `disableCloudEvents`, `assignmentLogger`, `unitKeyFn`, `contextFn`, `plugins`).

## Next steps

<CardGroup cols={2}>
  <Card title="Mobile experiment pattern" icon="mobile" href="/guides/canonical-experiments#mobile-app">
    Onboarding and in-app experiments.
  </Card>

  <Card title="How it works" icon="gears" href="/how-it-works">
    Evaluation modes and resolution.
  </Card>
</CardGroup>
