@sensorswave/react-native-sdk
v1.5.2
Published
Sensorswave React Native data tracking SDK
Readme
Sensors Wave React Native SDK
Sensors Wave React Native SDK is a mobile analytics tracking and AB testing library for React Native applications. If you're new to Sensor Wave, check out our product and create an account at sensorswave.com.
Requirements
- React Native >= 0.74.0
- React >= 18.0.0
Installation
npm install @sensorswave/react-native-sdk
# or
yarn add @sensorswave/react-native-sdkQuick Start
1. Initialize SDK
import { Sensorswave } from '@sensorswave/react-native-sdk';
// Initialize the SDK
Sensorswave.init('your-source-token', {
debug: false,
apiHost: 'https://your-api-host.com',
autoCapture: true,
enableClickTrack: true,
enableAB: true,
});
// Set user login ID (optional)
Sensorswave.setLoginId('user_12345');2. Wrap Your App with Provider
import { SensorswaveProvider } from '@sensorswave/react-native-sdk';
const App = () => {
return (
<SensorswaveProvider>
<NavigationContainer>
<Stack.Navigator>
{/* Your screens */}
</Stack.Navigator>
</NavigationContainer>
</SensorswaveProvider>
);
};3. Track Custom Events
import { Sensorswave } from '@sensorswave/react-native-sdk';
// Track a custom event
Sensorswave.trackEvent('ButtonClick', {
button_name: 'submit',
page: 'home',
});
// Or use the track method for more control
Sensorswave.track({
event: 'PurchaseCompleted',
properties: {
product_id: '12345',
amount: 99.99,
currency: 'USD',
},
});Configuration Options
| Option | Type | Default | Description | | ----------------- | ------- | ------- | ---------------------------------------------------------------------------------------- | | debug | boolean | false | Whether to enable debug mode for logging | | apiHost | string | '' | API host address for sending events | | autoCapture | boolean | true | Whether to automatically capture app events (install, start, end, page view, page leave) | | enableClickTrack | boolean | false | Whether to enable automatic click tracking | | enableCrashTrack | boolean | false | Whether to enable fatal crash tracking (fatal JS exceptions + native crashes, app only) | | enableErrorTrack | boolean | false | Whether to enable error auto-collection (non-fatal exceptions + unhandled rejections) | | enableAB | boolean | false | Whether to enable A/B testing feature | | batchSend | boolean | true | Whether to use batch sending (sends events in batches up to 10 events every 5 seconds) | | abRefreshInterval | number | 600000 | A/B test configuration refresh interval in milliseconds | | optOutCapturing | boolean | false | Disable all data collection until user consent. No read/write of user data on init | | persistOptOut | boolean | false | Persist the opt-out state across restarts. An explicit optOut/optIn call always wins |
Exception Tracking
Enable automatic exception reporting with two independent switches (both off by default, independent of autoCapture):
Sensorswave.init('your-source-token', {
enableCrashTrack: true, // fatal: crashes the process — fatal JS exceptions + native crashes (app only)
enableErrorTrack: true, // error: non-fatal uncaught exceptions + unhandled promise rejections (all platforms)
});- Fatal JS exceptions are persisted synchronously through the native bridge to the crash file (durable even as the JS runtime is torn down) and reported exactly once on the next launch with the original time back-filled. When the native bridge is unavailable, they fall back to in-session reporting with a forced flush (best effort).
- Native crashes (Android
UncaughtExceptionHandler— Java-level only, C++/JNI crashes are out of scope on Android; iOSNSException+ fatal signals) are written to a crash file synchronously, then reported on the next launch with the original crash time back-filled. RN-sourced JS crashes are filtered natively to avoid double reporting, and previous crash handlers (Crashlytics/Bugsnag/…) are chained, not replaced. - Calling
optOutCapturing()uninstalls the native crash handlers immediately — no crash data is written (or reported later) while opted out. - Auto-captured exceptions are deduplicated within a 30s window (same type + message).
Manual reporting of caught errors (always available, not gated by enableErrorTrack):
try {
riskyOperation();
} catch (e) {
Sensorswave.trackException(e, { context: 'checkout' }); // reports $Exception with level=error
}Navigation Tracking
The SDK provides automatic page view tracking for different navigation scenarios. Choose the method that fits your navigation setup:
Method 1: React Navigation v6 and below (Recommended)
Simply wrap your app with SensorswaveProvider. No additional configuration needed.
import { SensorswaveProvider } from '@sensorswave/react-native-sdk';
const App = () => {
return (
<SensorswaveProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
</SensorswaveProvider>
);
};How it works: The SDK uses useNavigationState hook internally to track route changes automatically.
Method 2: React Navigation v7+
For React Navigation v7+, use createNavigationRef to create a tracked navigation reference:
import { SensorswaveProvider, createNavigationRef } from '@sensorswave/react-native-sdk';
const navigationRef = createNavigationRef();
const App = () => {
return (
<SensorswaveProvider autocapture={{ captureScreens: false }}>
<NavigationContainer ref={navigationRef}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
</SensorswaveProvider>
);
};Why this is needed: React Navigation v7+ changed how navigation state is accessed, requiring a ref-based approach.
Method 3: Expo Router
Expo Router creates its NavigationContainer internally, so you cannot pass a ref to it directly. Use expo-router's useNavigationContainerRef to get the internal navigation reference and pass it to the Provider via the navigationRef prop:
import { useNavigationContainerRef } from 'expo-router';
import { SensorswaveProvider } from '@sensorswave/react-native-sdk';
export default function RootLayout() {
const navigationRef = useNavigationContainerRef();
return (
<SensorswaveProvider navigationRef={navigationRef}>
<Stack>
<Stack.Screen name="index" />
</Stack>
</SensorswaveProvider>
);
}Method 4: Manual Tracking
For custom navigation solutions or when automatic tracking isn't possible:
import { trackScreen } from '@sensorswave/react-native-sdk';
// Call this when screen changes
trackScreen('CustomScreen', {
title: 'Custom Screen Title',
custom_property: 'value',
});Navigation Tracking Comparison
| Navigation Type | Method | Automatic | Limitations |
| ----------------------------- | ----------------------------------------- | --------- | ------------------- |
| React Navigation v6- | SensorswaveProvider | ✅ Yes | None |
| React Navigation v7+ | createNavigationRef() or navigationRef prop | ✅ Yes | Requires ref setup |
| Expo Router | navigationRef prop | ✅ Yes | None |
| React Native Navigation (Wix) | Manual | ❌ No | Use trackScreen() |
| Custom Navigation | Manual | ❌ No | Use trackScreen() |
Customizing Screen Names
You can customize how screen names are captured:
<SensorswaveProvider
autocapture={{
captureScreens: true,
navigation: {
routeToName: (name, params) => {
// Custom logic for screen names
if (name === 'Detail') {
return `Product_${params?.productId}`;
}
return name;
},
routeToProperties: (name, params) => ({
// Add custom properties to page view events
category: params?.category || 'unknown',
}),
},
}}
>
{/* ... */}
</SensorswaveProvider>Disabling Automatic Screen Tracking
<SensorswaveProvider autocapture={false}>
{/* ... */}
</SensorswaveProvider>
// Or
<SensorswaveProvider autocapture={{ captureScreens: false }}>
{/* ... */}
</SensorswaveProvider>API Methods
Event Tracking
trackEvent
Manually track a custom event with properties.
Sensorswave.trackEvent('ButtonClick', {
button_name: 'submit',
page: 'home',
});track
Track an event with full control over the event structure.
Sensorswave.track({
event: 'PurchaseCompleted',
properties: {
product_id: '12345',
amount: 99.99,
},
});User Identification
identify
Set the login ID and send a binding event.
Sensorswave.identify('user_12345');setLoginId
Set the login ID without sending a binding event.
Sensorswave.setLoginId('user_12345');getLoginId
Get the current login ID.
const loginId = Sensorswave.getLoginId();getAnonId
Get the anonymous ID.
const anonId = Sensorswave.getAnonId();reset
Call when the user logs out to unbind the login ID from the device.
// When the user logs out
Sensorswave.reset(); // Keeps the anonymous ID by default
// To also reset the anonymous ID (e.g. shared devices)
Sensorswave.reset(true);reset(): Log out, keeping the anonymous ID (default)reset(true): Log out and reset the anonymous ID, generating a new one
User Profile
profileSet
Set user properties (overwrites existing values).
Sensorswave.profileSet({
name: 'John Doe',
age: 30,
plan: 'premium',
});profileSetOnce
Set user properties only if they don't exist.
Sensorswave.profileSetOnce({
signup_date: '2024-01-15',
initial_referrer: 'google',
});profileIncrement
Increment numeric user properties.
Sensorswave.profileIncrement({
login_count: 1,
points_earned: 100,
});profileAppend
Append values to list properties (no deduplication).
Sensorswave.profileAppend({
categories_viewed: ['electronics', 'mobile_phones'],
});profileUnion
Append values to list properties (with deduplication).
Sensorswave.profileUnion({
interests: ['technology', 'gaming'],
});profileUnset
Remove specific user properties.
Sensorswave.profileUnset(['old_plan', 'temp_id']);profileDelete
Delete all user profile data.
Sensorswave.profileDelete();Common Properties
registerCommonProperties
Register properties to be included with all events.
Sensorswave.registerCommonProperties({
app_version: '1.0.0',
environment: 'production',
});clearCommonProperties
Remove registered common properties.
Sensorswave.clearCommonProperties(['app_version']);A/B Testing
checkFeatureGate
Check if a feature flag is enabled.
const isEnabled = await Sensorswave.checkFeatureGate('new_feature');
if (isEnabled) {
// Show new feature
}getExperiment
Get experiment variant data. Returns an empty object {} if the experiment is not found or the variant is unset.
const experiment = await Sensorswave.getExperiment('homepage_layout');
if (experiment) {
applyLayout(experiment.layout_type);
}getFeatureConfig
Get feature configuration data. Returns an empty object {} if the config is not found or the variant is unset. When the server returns a JSON string, it will be parsed into an object automatically.
const config = await Sensorswave.getFeatureConfig('ui_config');
if (config.primaryColor) {
applyTheme(config.primaryColor);
}Privacy & Consent (Opt-out Capturing)
The SDK can disable all data collection until the user grants consent — useful for GDPR, CCPA, and other privacy regulations. While opted out, the SDK performs no reads or writes of user data: event tracking, identification, and profile calls become no-ops, and A/B test lookups return safe defaults (false / {}). registerCommonProperties / clearCommonProperties still work because they only update in-memory state.
optOutCapturing
Disable all data collection immediately. The batch sender is paused (already-queued events are kept but not dispatched). The state is persisted to local storage when persistOptOut: true. Safe to call before init.
Sensorswave.optOutCapturing();optInCapturing
Re-enable data collection and resume the batch sender. If the SDK was initialized with optOutCapturing: true (so the one-time setup was skipped), this runs the deferred initialization. The state is persisted when persistOptOut: true. Safe to call before init.
Sensorswave.optInCapturing();hasOptedOutCapturing
Return whether data collection is currently disabled. Returns the in-memory state when called before init.
if (Sensorswave.hasOptedOutCapturing()) {
// collection is disabled
}Typical Consent Flow
Initialize in the opt-out state, then enable collection after the user consents:
// Start disabled — collect nothing until consent
Sensorswave.init('your-source-token', {
apiHost: 'https://your-api-host.com',
optOutCapturing: true,
persistOptOut: true, // remember the choice across app restarts
});
// After the user grants consent in your consent dialog:
Sensorswave.optInCapturing();Utility Methods
destroy
Clean up SDK resources.
Sensorswave.destroy();Using Hooks
The SDK provides React hooks for convenient access:
useSensorswave
Get the SDK instance via hook.
import { useSensorswave } from '@sensorswave/react-native-sdk';
function MyComponent() {
const Sensorswave = useSensorswave();
const handlePress = () => {
Sensorswave.trackEvent('ButtonPressed', {
button: 'my_button',
});
};
return <Button onPress={handlePress} title="Track Event" />;
}Automatic Events
When autoCapture is enabled, the SDK automatically tracks:
| Event | Description |
| --------------- | --------------------------------------------------- |
| $AppInstall | Triggered on first app launch after installation |
| $AppStart | Triggered when app starts or comes to foreground |
| $AppEnd | Triggered when app goes to background |
| $AppPageView | Triggered when navigating to a new screen |
| $AppPageLeave | Triggered when leaving a screen (includes duration) |
When enableClickTrack is enabled:
| Event | Description |
| ----------- | --------------------------- |
| $AppClick | Triggered on element clicks |
Click Tracking ($AppClick)
The SDK provides automatic click tracking when enableClickTrack: true is set during initialization.
Automatically Tracked Components
The SDK automatically tracks clicks on the following React Native components:
| Component Type | Examples | Description |
| ------------------------ | ---------------------------------------- | ----------- |
| Touchable Components | TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback | Automatically tracked |
| Pressable | <Pressable> | Automatically tracked |
| Button | <Button> | Automatically tracked |
Basic Usage
Just use components normally and the SDK will automatically track click events:
<Pressable onPress={handleSubmit}>
<Text>Submit</Text>
</Pressable>
<TouchableOpacity onPress={handleLogin}>
<Text>Login</Text>
</TouchableOpacity>
<Button title="Confirm" onPress={handleConfirm} />Disabling Tracking for Specific Elements
To prevent specific elements from being tracked, add the sw-no-capture prop:
<TouchableOpacity
sw-no-capture
onPress={handleInternalAction}
>
<Text>Internal Action</Text>
</TouchableOpacity>Ignoring Component Types
You can configure which component types to ignore:
<SensorswaveProvider
autocapture={{
clickTrack: {
ignoreLabels: ['ScrollView', 'FlatList', 'View'], // These won't be tracked
allowedElementTypes: ['TouchableOpacity', 'Pressable', 'Button'],
},
}}
>
<App />
</SensorswaveProvider>Custom Props Collection
Collect custom props from clicked elements:
<SensorswaveProvider
autocapture={{
clickTrack: {
propsToCapture: ['variant', 'size', 'color'], // Custom props to include
},
}}
>
<App />
</SensorswaveProvider>Result: Custom props will be included in $AppClick events along with standard properties.
Complete Example
import React from 'react';
import { View, TouchableOpacity, Text, Pressable } from 'react-native';
import { SensorswaveProvider } from '@sensorswave/react-native-sdk';
function App() {
return (
<SensorswaveProvider
autocapture={{
clickTrack: {
ignoreLabels: ['ScrollView', 'FlatList', 'View'],
allowedElementTypes: ['TouchableOpacity', 'Pressable', 'Button'],
propsToCapture: ['variant', 'color'],
},
}}
>
<MyScreen />
</SensorswaveProvider>
);
}
function MyScreen() {
return (
<View>
{/* Automatically tracked - TouchableOpacity */}
<TouchableOpacity
variant="primary"
onPress={handleSubmit}
>
<Text>Submit Form</Text>
</TouchableOpacity>
{/* Automatically tracked - Pressable */}
<Pressable onPress={handleCancel}>
<Text>Cancel</Text>
</Pressable>
{/* Not tracked - has sw-no-capture */}
<TouchableOpacity
sw-no-capture
onPress={handleInternal}
>
<Text>Internal</Text>
</TouchableOpacity>
</View>
);
}Event Data Example
When user clicks a button:
{
"event": "$AppClick",
"properties": {
"$screen_name": "FormScreen",
"$element_type": "TouchableOpacity",
"$element_content": "Submit Form",
"$element_selector": "View > TouchableOpacity > Text [Submit Form]",
"$element_path": "View > TouchableOpacity > Text > [Submit Form]",
"variant": "primary"
}
}UTM Parameter Collection
To collect UTM parameters from Deep Links, you need to configure your app to handle Deep Links.
iOS Deep Link Setup
1. Configure URL Scheme
Add URL Scheme configuration in ios/YourApp/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.yourapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
<key>CFBundleURLTypeRole</key>
<string>Editor</string>
</dict>
</array>2. Configure AppDelegate
Add Deep Link handling methods in ios/YourApp/AppDelegate.swift:
// MARK: - Deep Linking / URL Scheme handling
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
return RCTLinkingManager.application(app, open: url, options: options)
}
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}3. Rebuild
cd ios
pod install
cd ..
yarn iosWhy this is needed: iOS requires AppDelegate to be the entry point for Deep Link events. This cannot be automated at the SDK level due to iOS platform architecture.
Android Deep Link Setup
Add intent filter in android/app/src/main/AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:exported="true">
<!-- Existing intent filter for LAUNCHER -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep Link intent filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>Note: React Native's Linking API automatically handles Deep Links on Android once the intent filter is configured.
Troubleshooting
Page Views Not Tracking
- Make sure
SensorswaveProviderwraps yourNavigationContainer - For React Navigation v7+, use
createNavigationRef() - Check that
autoCaptureis not disabled
Events Not Sending
- Check your
apiHostconfiguration - Enable
debug: trueto see logs
License
Apache-2.0
