React Native SDK

For account IDs, backend-verified guests, token expiry, property updates, and logout, see the identity integration guide. The lifecycle APIs documented there require 0.1.4 or later on the stable channel, or 0.2.0-beta.2 or later on the Expo next channel.

v0.1.4   React ≥ 18 · React Native ≥ 0.72

The React Native SDK ships the full userGist surface — identify, track, surveys, push, feature requests — as plain async TypeScript. It runs against AsyncStorage for queueing and integrates with native push (APNs on iOS, FCM on Android) via a thin bridge.

Startup presentation readiness

Initialize with presentationPaused: true at app launch. Analytics, consent, identity, and networking continue while campaign UI waits. After the existing startup loading and navigation have finished and the loaded screen is visible, call resumePresentation(). Mount any required UserGist UI provider before that callback. Readiness must work for both anonymous and identified users.

Call pausePresentation() before another flow that must not be interrupted. Pausing does not dismiss an already visible SDK surface. Queued feedback, surveys, and in-app messages are discarded if their consent is withdrawn or the user changes, even if consent is granted again before resuming. Repeated initialization keeps the first readiness setting; repeated resume calls do not show the same queued work twice. The option defaults to false for existing integrations, so upgrading alone does not enable startup deferral.

Do not resume from a splash screen, an app-root mount that still shows loading, a disappearing screen, or a fixed timer. Use the host's existing completion callback; the SDK cannot infer when arbitrary startup navigation has finished.

// After the loaded screen and startup navigation are ready:
UserGist.resumePresentation()

Install

pnpm add @usergist/feedback-react-native@latest @react-native-async-storage/async-storage react-native-safe-area-context
# or
npm install @usergist/feedback-react-native@latest @react-native-async-storage/async-storage react-native-safe-area-context

The iOS application target must compile Swift to link the native bridge's runtime support. If the app target contains only Objective-C/Objective-C++, add UserGistSwiftSupport.swift containing import Foundation to that application's Compile Sources. Apps that already compile Swift need no extra file. Preserve existing bridging headers.

Then install pods on iOS:

cd ios && pod install

The stable latest channel is 0.1.4. For Expo, use the prerelease next channel described in the Expo guide. Verify the resolved version in your lockfile and rebuild after upgrading.

Peer dependencies

PeerRequired version
react>= 18
react-native>= 0.72
@react-native-async-storage/async-storage>= 1.19
react-native-safe-area-context>= 4

Initialise

Call UserGist.init exactly once, before anything else. The earliest safe place is the top of your entry file.

// App.tsx
import { UserGist } from '@usergist/feedback-react-native'
 
UserGist.init({
  writeKey: 'rk_live_REPLACE_ME',
  environment: __DEV__ ? 'development' : 'production',
  debug: __DEV__,
  presentationPaused: true,
})

Init options

OptionTypeDefaultNotes
writeKeystringRequired. From App settings → SDK keys.
environment'production' | 'staging' | 'development''production'Tagged on every event for filtering.
apiUrlstringhttps://api.usergist.comProduction overrides must use HTTPS.
debugbooleanfalseLogs queue activity to the JS console.
presentationPausedbooleanfalsePass true at launch; resume when the host is ready.

Identity

// Your authenticated backend exchanges its rtk_ token at
// POST /v1/apps/:appId/sdk/subject-tokens and returns only subjectToken.
const { subjectToken } = await yourBackend.getUserGistSubjectToken()
 
// Once you know the user
UserGist.identify('user_42', {
  plan: 'pro',
  signedUpAt: '2026-01-12T00:00:00Z',
}, subjectToken)
 
// On logout
UserGist.reset()

Events

UserGist.track('checkout_completed', {
  orderId: 'ord_991',
  amountUsd: 49,
})

Ordinary analytics events queue in AsyncStorage and flush every 15 s, every 100 events, or when you call UserGist.flush() explicitly. They survive app restarts. With the immediate-delivery API and SDK release, known server-dependent engagement triggers flush immediately while retaining the order of earlier events. The ingest response can carry authorized feedback, in-app messages or surveys, avoiding the normal batch and instruction-poll wait even with web plus mobile enabled.

If user IDs or queued properties require encryption at rest, configure a Keychain/Keystore-backed adapter before init():

UserGist.setStorageAdapter(encryptedStorageAdapter)
UserGist.init(config)

User properties and personalized actions

Update saved fields for the active anonymous or identified user after granting analytics consent:

const result = await UserGist.setUserProperties({ first_name: 'Ava', country: 'IL' })
// 'synced', 'queued' for retry, or 'rejected'
await UserGist.setUserProperties({}, ['first_name']) // Explicit unset

Use flat string, number, boolean or null values; sensitive keys follow the app's privacy allow-list. Properties and tracked event fields can personalize content and typed JSON actions. Register in-app actions through UserGist.setInAppHandlers({ onJsonAction }) and push actions through Push.setHandlers({ onJsonAction }). Your app validates the received data and opens its own destination. See Personalize messages for selecting the latest watched show and preserving a string ID with a numeric playback position.

userGist treats consent as a transport hard gate. Queued work is purpose-tagged, only allowed purposes are transmitted, and withdrawal removes disallowed pending work. Wire it to your privacy controls.

await UserGist.setConsent({
  analytics: true,
  feedback: true,
  push: false,
  survey: true,
})

Surveys

The SDK lets you trigger surveys imperatively, or rely on event-triggered surveys configured in the dashboard.

const available = await UserGist.getAvailableSurveys()
if (available.length > 0) {
  UserGist.openSurvey(available[0].id, { language: 'en' })
}
 
// React to survey lifecycle events
UserGist.setSurveyHandlers({
  onComplete: (surveyId, attemptId) => {
    console.log('Survey complete', surveyId, attemptId)
  },
})

Survey delivery and recovery

Triggered surveys can include an authorized attempt in the delivery response, so display does not wait for another start request or unrelated queued uploads. Keep the Provider mounted, grant survey consent, and wait for the initial rule refresh before relying on cached matching.

Eligible cached surveys save their attempt locally before display. These must be repeatable, uncapped and non-personalized, with a server-signed start permission valid for ten minutes. An attempt started during that interval can synchronize for seven days. Campaign edits do not instantly revoke a cached permission. Expired permissions and server-dependent surveys use the online authorization path.

With persistent storage available, the SDK saves answers and the original survey content under the same identity and attempt, allowing reopening after app termination. Starts and progress retry through the durable queue. Completion still uses its normal submission/retry result; it is not an instant offline send. Reset clears cached sessions, and stale identity or consent responses cannot reopen a survey. Storage failures are reported rather than pretending an attempt was saved.

Custom setSurveyHandlers({ onInvite }) handlers keep control of when an invite becomes a start; those deliveries do not pre-create an attempt. On-demand and deep-link surveys retain their existing server resume flow. New personalized surveys require online field resolution.

If you share a survey URL (e.g. via email), handle the deep link from your linking handler:

import { Linking } from 'react-native'
 
Linking.addEventListener('url', ({ url }) => {
  UserGist.handleSurveyDeepLink(url)
})

Feature requests board

Render the public roadmap inside your app — votes, comments, status pills, the lot.

import { UserGist } from '@usergist/feedback-react-native'
 
// Open the full board UI
UserGist.openRequestsBoard()
 
// Or open a single request
UserGist.openRequestDetail('req_abc123')
 
// Or build your own UI
const { items } = await UserGist.getRequests({ statuses: ['planned'] })
await UserGist.voteOnRequest('req_abc123', true)
await UserGist.postComment('req_abc123', 'Please ship this!')

Push notifications

userGist sends through APNs (iOS) and FCM (Android). The SDK manages tokens, channels, and delivery acks.

iOS — notification service extension

To track delivery (not just opens), add a Notification Service Extension target and include the package extension helper source. Configure its Info.plist with the same API/write-key values as the host. See the package README and demo app for the current exact native target wiring; the extension target cannot read React Native AsyncStorage directly.

Android — channels

On Android 8+, every notification needs a channel. Register the SDK's defaults (or your own) at start-up.

The bundled service creates a fallback channel and honors a configured usergist_channel_id when the host has created it. If the app already owns an FCM service, disable the bundled service with userGistFirebaseServiceEnabled=false and forward tokens/messages through Push.

Wire push end-to-end

// 1. Ask for permission and register a token
await UserGist.enablePush({ environment: 'production' })
 
// 2. (Optional) listen for events
UserGist.onPushEvent((evt) => {
  // evt.kind: 'displayed' | 'opened' | 'dismissed' | ...
})
 
// 3. On logout — invalidate the token
await UserGist.disablePush()

See Guides → Send your first push for the dashboard side of the loop.

Theming

Override survey, prompt, and request board styles to match your brand.

UserGist.setThemeOverrides({
  colors: {
    primary: '#6C5CE7',
    background: '#0B1220',
    text: '#FFFFFF',
    subtext: '#C7CEDA',
    border: '#2B3547',
  },
  radius: 16,
})

Public surface (quick reference)

MethodWhat it does
init(opts)Boots the SDK. Call once.
identify(id, props?, subjectToken)Securely link the device to a stable user ID.
track(name, props?)Queue a behavioural event.
setConsent(c)Gate analytics / feedback / push / survey.
reset()Clear state, rotate anonymous ID.
flush()Force-flush the event queue.
setDebug(bool)Toggle console logging.
setDiagnosticHandler(cb)Receive bounded, PII-free SDK health diagnostics.
setStorageAdapter(adapter)Use host-provided encrypted persistence; call before init.
setThemeOverrides(theme)Brand the in-SDK UI.
getAnonymousId()The on-device anonymous ID (string).
onPromptShown(cb)Fires when a prompt is rendered.
onResponse(cb)Fires on any prompt/survey response.
onPushEvent(cb)Fires on push lifecycle events.
getAvailableSurveys()List currently-targeted surveys.
openSurvey(id, context?)Render a survey.
handleSurveyDeepLink(url)Open a survey from a deep link.
setSurveyHandlers(handlers)Custom render hooks.
getRequests(opts)List feature requests.
submitRequest(...)Create one.
voteOnRequest(id, vote) / followRequest(id, follow)Add or remove engagement.
getComments(id) / postComment(...) / editComment(...) / deleteComment(...)Threaded comments.
openRequestsBoard() / openRequestDetail(id)Render the request board.
enablePush(opts) / disablePush()Push lifecycle.
registerPushToken(token, platform)Manual token registration (if you handle FCM/APNs yourself).
rebindPushToken()Re-bind after a reset().
pushBeacon(kind, deliveryId)Manual delivery beacon.
pushAppOpen()Tell us your app reopened from a push.
pushFetchChannels() / pushSetChannelSubscription(...)Channel preferences.

What's next