# React Native SDK

@cohorly/react-native is a standalone React Native client. It has no hard dependency on react-native or any storage library - it works out of the box with an in-memory store and upgrades to persistent storage when you inject one.

## Install

```bash
pnpm add @cohorly/react-native
# recommended, for persistence across app restarts
pnpm add @react-native-async-storage/async-storage
```

## Initialize

Call `init()` once near app launch. Inject AsyncStorage so the distinct id,
queued events, and super properties survive restarts.

```ts
import AsyncStorage from "@react-native-async-storage/async-storage";
import { init, track, identify, people } from "@cohorly/react-native";

init({
  token: "YOUR_PROJECT_TOKEN",
  storage: AsyncStorage, // omit to fall back to in-memory storage
});

track("Screen Viewed", { screen: "Home" });
identify("user_123");
people.set({ plan: "pro" });
```

Without `storage`, the client uses `InMemoryStorage` (also exported),
which works but loses the distinct id, queue, and super properties on
every app restart.

## API

| Member                                  | Signature                                        | Notes                                                                                                                                                                                                     |
| --------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `init`                                  | `(options) => CohorlyClient`                     | All optional: `token`, `apiHost` (defaults to the hosted API), `storage`, `flushInterval`, `flushAt`, `fetch`, `disabled`, `loadFeatureFlags`, `sendExposureEvents`, `flagPersistence`, `flagCacheTtlMs`. |
| `track`                                 | `(event, properties?) => void`                   | Queue an event.                                                                                                                                                                                           |
| `identify`                              | `(distinctId) => void`                           | Switches identity synchronously, then posts `/alias` (best effort) when the previous id was anonymous.                                                                                                    |
| `reset`                                 | `() => void`                                     | Clears identity, super properties, and un-flushed events.                                                                                                                                                 |
| `register` / `unregister`               | `(props)` / `(key)`                              | Super properties merged into every event.                                                                                                                                                                 |
| `people.*`                              | `set / setOnce / increment / unset / deleteUser` | Queued and sent to /engage.                                                                                                                                                                               |
| `flush`                                 | `() => Promise<void>`                            | Manual flush.                                                                                                                                                                                             |
| `getDistinctId`                         | `() => string`                                   | Current distinct id.                                                                                                                                                                                      |
| `getFeatureFlag` / `isFeatureEnabled`   | `(key, fallback?)`                               | Flag reads; optional fallback for unknown/unloaded flags (never an exposure).                                                                                                                             |
| `getFeatureFlagDetails`                 | `(key, fallback?) => FlagDetails`                | Full result with `source` (`"network"` / `"persistence"` / `"fallback"`) plus `ttlMs` - and `persistedAt` when the cache carries `stored_at` - when served from the cache.                                |
| `getFeatureFlagPayload`                 | `(key) => unknown \| null`                       | Variant payload.                                                                                                                                                                                          |
| `reloadFeatureFlags` / `onFeatureFlags` | `() => Promise<void>` / `(cb) => unsub`          | Re-evaluate; subscribe to updates.                                                                                                                                                                        |

Flags are persisted per identity (`cohorly:flags`, scoped by `distinct_id`) and
bounded by `flagCacheTtlMs` (default 86400000 ms, 24 h). `flagPersistence`
selects the policy - `"persistenceUntilNetworkSuccess"` (default, still
serves an expired cache up to 7 days stale), `"networkFirst"` (strictly
within the TTL), or `"networkOnly"` (never touches the cache). A legacy
cache without `stored_at` is served indefinitely until a successful reload
rewrites it. See
[Feature flags](/feature-flags#flag-persistence).

Note the People delete method here is `people.deleteUser()` (not `delete`).
`identify()` applies the identity switch synchronously and then fires an
`/alias` post in the background: it is never awaited, and a failure is
swallowed rather than retried. That is safe because every event already
carries `$device_id` and, once identified, `$user_id`, so the server links
the anonymous and identified ids implicitly from the next `track()` call
even if the `/alias` post never lands. See
[Identity](/reference/data-model#identity). Call `reset()` on logout so a
shared device doesn't carry one person's identity into the next session.

## Batching

Events are queued and flushed automatically every 5 seconds or once 20
events have accumulated - both configurable via `flushInterval` and
`flushAt`. Queued events are persisted to storage, so nothing is lost if
the app closes before a flush. You can also construct
`new CohorlyClient(options)` directly for multiple instances or dependency
injection.
