Skip to main content
@trevosdk/react-native runs experiments and records events in React Native and Expo apps, assigning the same variants the web SDK does for the same user. Pure TypeScript — no native module, no linking step, no config plugin. Expo:
Bare React Native:
AsyncStorage is a required peer, not an optional one. Without persistence the anonymous id is re-minted on every launch, which re-buckets every user and corrupts any running experiment.

Quick start

Create the client in its own module. This example uses Expo’s public environment convention; in bare React Native, use the app’s existing runtime-config adapter.
Keep the app’s existing splash/loading boundary visible while the hook returns null, so AsyncStorage hydration cannot flash control to a treatment user. If auth restores a known user, call trevo.identify(user.id) before mounting this experiment UI.
Use a publishable key (tsk_live_…). A shipped binary can be unpacked, so a secret key in an app is a leaked key.

Read this before you plan your first mobile experiment

Three properties of mobile are not choices we made, and every vendor shares them. Planning around them is easier than discovering them mid-experiment. A mobile cohort ramps over weeks, not days. Experiment code ships inside an app release, so your population is whoever has updated. Web reaches full traffic within a day of activation; mobile climbs as adoption climbs, and both variants coexist across app versions for as long as old builds stay installed. An experiment can be switched off without a release, but not changed. Config is re-read on every foreground, so pausing or stopping is immediate. Changing what a variant does is code, and code ships through App Store review. Anonymous users are per-device. A visitor who browses on a laptop and then opens the app is two participants until they sign in. identify() links them from that point forward; nothing can link them retroactively. Identifying a user can change their arm because all SDKs assign the signed-in user from the same user id. Restore auth and call identify() before showing experiment UI; the onChange subscription above re-renders if identity changes later.

Durability

Phones lose the network constantly, are killed without warning, and have wrong clocks more often than desktops. The SDK is built around that:
  • Events survive a cold start. The queue is written to AsyncStorage and replayed on next launch. Delivery is at-least-once, and every event carries an idempotency key so a replay is recorded once.
  • Timestamps survive a wrong clock. Each batch is stamped with sentAt when it leaves the device. Ingestion compares that against its own clock to recover the device’s offset and correct every event in the batch — which is what lets a queue drained three days late land on a real timeline. Nothing is dropped for being out of range.
  • Assignment survives no network at all. Config is persisted, so a cold start in a lift assigns the same variant it assigned yesterday instead of falling back to control and silently switching once the network returns.
  • A signed-in identity survives a relaunch. identify() persists the user id, so the next cold start buckets on it rather than reverting to the anonymous id and splitting one person across two identities.

Gating on app version

An experiment whose variant code only exists from a given build onward should not enrol devices that cannot render it. Set minAppVersion on the experiment:
Builds below the threshold get control and record no exposure, so they never appear in the results. This requires you to pass appVersion when creating the client — without it, a gated experiment returns control for everyone, because the SDK cannot prove the build is new enough and guessing wrong means a broken screen. Always pass the installed binary’s real app version, never a copied sample. Expo apps can read it from expo-application (npx expo install expo-application):
For bare React Native, pass the value from the build-metadata adapter the app already uses. Omit it when the workspace does not use minAppVersion; do not hard-code it.

Options

API summary

Expo

Works in Expo Go and in development builds with no config plugin — there is no native code to link. Install AsyncStorage the Expo way:

Store compliance

Apple. The package ships ios/PrivacyInfo.xcprivacy. Xcode aggregates third-party manifests automatically — an app embedding an SDK without one is rejected at submission. It declares product interaction and a user id, both linked, neither used for tracking, and no required-reason APIs. Google Play. Data safety answers you can paste into the Play Console: The anonymous id is minted on device and is not an advertising or hardware identifier, which is why “Device or other IDs” is No. Declaring it there invites a policy review you do not need.

Correctness

This package runs the shared conformance vectors against its public API in CI, so a change that would bucket a user differently from the browser fails the build. The normative rules are in the bucketing spec.