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-contextThe 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 installThe 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
| Peer | Required 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
| Option | Type | Default | Notes |
|---|---|---|---|
writeKey | string | — | Required. From App settings → SDK keys. |
environment | 'production' | 'staging' | 'development' | 'production' | Tagged on every event for filtering. |
apiUrl | string | https://api.usergist.com | Production overrides must use HTTPS. |
debug | boolean | false | Logs queue activity to the JS console. |
presentationPaused | boolean | false | Pass 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 unsetUse 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.
Consent
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.
Deep-link surveys
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)
| Method | What 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. |