@insightech/react-native
v1.2.0
Published
Insightech analytics SDK for React Native
Downloads
431
Readme
@insightech/react-native
Insightech analytics SDK for React Native. Captures user interactions, screen views, and component tree snapshots for session replay, heatmaps, click maps, scroll depth, and form analytics on the Insightech dashboard.
The SDK sends data in the exact same format as the Insightech web JavaScript SDK, so no backend changes are needed.
Table of Contents
- Requirements
- Installation
- Quick Start (2 Steps)
- What Gets Tracked Automatically
- Custom Event Tracking
- Error Tracking
- Form Tracking
- Element Identification with testID
- Privacy & Data Masking
- Configuration Reference
- Advanced Usage
- How It Works
- Troubleshooting
- API Reference
Requirements
- React >= 17
- React Native >= 0.68
@react-native-async-storage/async-storage>= 1.17- Optional:
@react-navigation/nativefor automatic screen tracking
Compatible with: Expo managed workflow, Expo bare workflow, React Native CLI, Expo Go, and the New Architecture (Fabric/Bridgeless). No native modules required.
Tested with: React Native 0.68 through 0.81, React 17 through 19, both Paper and Fabric renderers. React Native 0.76–0.81 are verified on every push by the CI matrix (TypeScript build + full unit-test suite, against each version's bundled types and its matching React 18/19). React Native 0.81 (React 19, New Architecture) is additionally verified end-to-end on both the iOS simulator and an Android emulator via the example/ app — pageview, tap, scroll, text input, navigation, and FlatList all capture correctly on each. RN 0.77–0.80 are covered by the CI matrix at the build + unit-test level; on-device verification focuses on the newest minor, where fiber-tree and New-Architecture changes are most likely to surface.
New Architecture: The SDK accesses React internals defensively (see #15) and runs on both the legacy (Paper) and New Architecture (Fabric/Bridgeless) renderers. The CI matrix exercises the default architecture shipped with each RN version.
Installation
npm install @insightech/react-native @react-native-async-storage/async-storageIf using @react-navigation/native (recommended for automatic screen tracking):
npm install @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-contextQuick Start
Integration requires just two steps: add the Babel plugin and wrap your app with InsightechProvider. No component swapping needed.
Step 1: Add the Babel Plugin
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'], // or 'module:metro-react-native-babel-preset'
plugins: ['@insightech/babel-plugin-react-native'],
};
};The plugin automatically instruments interactive components at build time. Your app code stays 100% standard React Native.
Step 2: Wrap Your App
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { InsightechProvider } from '@insightech/react-native';
export default function App() {
const navigationRef = useNavigationContainerRef();
return (
<InsightechProvider
config={{
account: 'YOUR_PROFILE_ID:YOUR_SERVER_ID',
appName: 'MyApp',
}}
navigationRef={navigationRef}
>
<NavigationContainer ref={navigationRef}>
{/* Your app navigator */}
</NavigationContainer>
</InsightechProvider>
);
}Where to find your account string: Log in to the Insightech dashboard. Your profile ID and server ID are in the tracking code snippet, formatted as
profileId:serverId.
That's it. The SDK starts tracking immediately.
What Gets Tracked Automatically
Once set up, the following are captured without any additional code:
| What | How | Requires |
|------|-----|----------|
| Screen views | React Navigation listener | navigationRef prop |
| Tap / click events | Babel plugin instruments Pressable, TouchableOpacity, TouchableHighlight | Babel plugin |
| Scroll events | Babel plugin instruments ScrollView, FlatList, SectionList | Babel plugin |
| Text input & changes | Babel plugin instruments TextInput | Babel plugin |
| Field focus / blur | Tracked via TextInput wrapper | Babel plugin |
| App background / foreground | AppState listener | Nothing (built-in) |
| Viewport resize | Dimensions listener | Nothing (built-in) |
| Component tree snapshots | React fiber tree serialization | Nothing (built-in) |
| DOM mutations | Tree diff after interactions | Nothing (built-in) |
| Rage taps | Rapid repeated taps on same element | Nothing (built-in) |
| JS crashes | Global error handler | Nothing (built-in) |
| API errors | Fetch/XHR interception | Server config |
Excluding Components from Auto-Tracking
If you don't want certain components to be tracked, exclude them in the Babel plugin config. Two options are available and can be combined:
exclude— skip specific component types everywhere.excludeFiles— skip all instrumentation for files matching glob patterns. Use this as a file-level escape hatch for a screen or directory that interacts badly with instrumentation (e.g. a screen with complex native modules), instead of disabling the plugin entirely.
plugins: [
['@insightech/babel-plugin-react-native', {
// Skip these component types in every file
exclude: ['ScrollView', 'FlatList'],
// Skip these files/directories entirely (glob patterns)
excludeFiles: ['**/lesson/**', '**/VideoPlayer.tsx'],
}],
],Glob support: ** (any path segments), * (any characters within one segment), and ? (a single character). Globs are matched against the file's absolute path, so prefix directory patterns with **/. Clear the Metro cache after changing plugin options (--reset-cache / --clear).
Note:
TouchableOpacityandTouchableHighlightare wrapped by faithful tracked equivalents that preserve their native press feedback (opacity fade / underlay highlight) — instrumentation is transparent and does not change how your buttons look or behave.
Custom Event Tracking
Use the useInsightech hook to track custom business events:
import { useInsightech } from '@insightech/react-native';
function ProductDetailScreen({ product }) {
const { trackCustomEvent } = useInsightech();
const handleAddToCart = () => {
addToCart(product);
trackCustomEvent({
event: 'add_to_cart',
product_id: product.id,
product_name: product.name,
price: product.price,
currency: 'USD',
});
};
return (
<Pressable testID="add-to-cart-btn" onPress={handleAddToCart}>
<Text>Add to Cart</Text>
</Pressable>
);
}Custom events appear as type 99 (DATA_LAYER) in the session timeline and can be used for conversion tracking, funnel analysis, and segmentation.
Error Tracking
Automatic JS Crash Tracking
The SDK automatically captures unhandled JavaScript exceptions via React Native's ErrorUtils. These are sent as js_error events and appear in the session timeline. The handler chains with existing error handlers (Sentry, Crashlytics, etc.) so it won't interfere with your crash reporting setup.
Manual Error Tracking
Track error messages shown to users — validation errors, API failures, or any error state:
const { trackError } = useInsightech();
// Form validation error
trackError({
message: 'Please fill in all required fields',
type: 'validation',
context: { screen: 'Checkout', empty_fields: ['email', 'phone'] },
});
// API error
trackError({
message: 'Payment failed: card declined',
type: 'api',
context: { endpoint: '/api/payment', status: 402 },
});Each trackError call sends a custom event with event: "error", error_message, error_type (defaults to "validation"), and any additional context fields.
Form Tracking
Track form submissions with field-level detail:
const { trackFormSubmit, flush } = useInsightech();
const handleCheckout = async () => {
trackFormSubmit({
nodeIndex: 0,
cssPath: '#checkout-form',
name: 'checkout',
method: 'POST',
fields: [
{ nodeIndex: 1, cssPath: '#email', name: 'email', value: email },
{ nodeIndex: 2, cssPath: '#name', name: 'name', value: name },
// Mask sensitive fields before tracking
{ nodeIndex: 3, cssPath: '#card', name: 'card', value: '****' },
{ nodeIndex: 4, cssPath: '#cvv', name: 'cvv', value: '***' },
],
});
// Force send events before navigating away
await flush();
navigation.navigate('OrderConfirmation', { orderId });
};Important: Always call
flush()before navigating away from a screen with tracked form data to ensure events are sent before the component unmounts.
Element Identification with testID
Add testID to your components for stable, readable element identification in analytics:
<Pressable testID="add-to-cart" onPress={handleAdd}>
<Text>Add to Cart</Text>
</Pressable>
<TextInput testID="email-input" placeholder="Email" onChangeText={setEmail} />
<ScrollView testID="product-list">
{/* ... */}
</ScrollView>Why this matters:
testIDmaps to the HTMLidattribute in the synthetic DOM, producing CSS selectors like#add-to-cart- Without
testID, elements are identified by position (e.g.,div:nth-child(2)), which breaks when the layout changes testIDis also used for content masking rules (see Privacy section)testIDis already standard practice for testing — use the same IDs for both
Privacy & Data Masking
All personal data is masked on-device before transmission. Raw values never leave the device.
Automatic Input Masking
All TextInput values are masked by default:
| User types | Sent as | Rule |
|------------|---------|------|
| John Doe | **** *** | Letters/symbols → * |
| 4242424242424242 | 0000000000000000 | Digits → 0 |
| [email protected] | ****@*******.** | Email pattern detected, structure preserved |
| 123 Main St | 000 **** ** | Mixed: digits → 0, letters → * |
Automatic Email Detection
Email addresses are detected and masked in all text content (not just inputs). Any text matching an email pattern is masked before the component tree is sent.
Content Masking with testID
Mask specific on-screen text using testID combined with the server's contentBlockList:
{/* Order ID — visible (not in block list) */}
<Text testID="order-id">{orderId}</Text>
{/* Personal details — masked via contentBlockList */}
<Text testID="personal-details">{userName}</Text>
<Text testID="personal-details">{userEmail}</Text>
<Text testID="personal-details">{userAddress}</Text>In the replay:
- Order ID:
ORD-12345(readable) - Name:
**** ***(masked) - Email:
****@*******.**(masked) - Address:
*** **** **(masked)
Server-Controlled Masking Rules
The server returns two lists on the SDK's first request — no app update needed to change masking rules:
fieldAllowList— Input fields to leave unmasked (e.g.,["#search-input", "#quantity"])contentBlockList— Elements to fully mask (e.g.,["#personal-details", "#payment-info"])
Configuration Reference
All configuration options for InsightechProvider:
<InsightechProvider
config={{
// ── Required ──
account: 'profileId:serverId', // From your Insightech dashboard
appName: 'MyApp', // Used in screen URLs
// ── Optional ──
protocol: 'https:', // Server protocol (default: 'https:')
trackingLevel: 'full', // 'full' or 'lite' (default: 'full')
sampleRate: 1.0, // Fraction of visitors with session replay, 0.0–1.0 (default: 1.0)
devMode: false, // true = new visitor ID on each app start,
// enables console logging (default: false)
// ── Network ──
maxConcurrentRequests: 1, // Max parallel HTTP requests (default: 1)
sendRequestSize: 200, // KB threshold to trigger send (default: 200)
requestTimeout: 10000, // Request timeout in ms (default: 10000)
maxRetries: 3, // Max retries per failed batch (default: 3)
retryBaseDelay: 1000, // Base delay for exponential backoff in ms (default: 1000)
// ── Event Throttling ──
scrollInterval: 150, // Scroll event throttle in ms (default: 150)
resizeInterval: 150, // Resize event throttle in ms (default: 150)
mutationBatchInterval: 200, // Mutation batch window in ms (default: 200)
// ── Storage ──
visitorIdStorageKey: 'insightech_vid', // AsyncStorage key for visitor ID
maxQueueSize: 1000, // Max queued events before dropping oldest (default: 1000)
maxTestIdEntries: 5000, // Max testID lookup entries before LRU eviction (default: 5000)
maxSnapshotBytes: 500000, // Drop a DOM snapshot above this approx. size (default: 500000)
deferTracking: true, // Defer snapshot/gzip work off the critical path (default: true)
}}
navigationRef={navigationRef} // Optional: React Navigation ref for auto screen tracking
/>Session Sampling
sampleRate controls session-replay coverage, not whether events flow. Analytics events (taps, scrolls, inputs, custom events, errors) are always sent for every visitor; the rate only decides which visitors also capture replay data (DOM snapshots and mutations):
| sampleRate | Visitors with full tracking (replay) | Visitors with lite tracking (events only) |
|---|---|---|
| 1.0 (default) | 100% | 0% |
| 0.5 | 50% | 50% |
| 0.0 | 0% | 100% |
The decision is deterministic per visitor — a visitor keeps the same mode across app launches, and the same calculation is used by other Insightech SDKs, so a visitor lands in the same bucket on every platform.
Precedence when multiple settings apply:
- Explicit
trackingLevel— if you settrackingLevel: 'full'or'lite', sampling is bypassed entirely. - Server override (
sr) — Insightech can adjust the rate server-side without an app release. sampleRate— the local default,1.0if omitted.
<InsightechProvider
config={{
account: 'YOUR_PROFILE_ID:YOUR_SERVER_ID',
appName: 'MyApp',
sampleRate: 0.25, // replay for 25% of visitors; analytics for all
}}
/>Out-of-range values are clamped to [0, 1] (a warning is logged in devMode).
Snapshot Size Guard
On very deep or wide screens a single DOM snapshot can grow into megabytes — heavy to serialize, expensive to gzip, and a large one-shot hit to a user's bandwidth. maxSnapshotBytes (default 500000, an approximate serialized-size estimate) bounds this: when a snapshot would exceed the limit, the SDK drops that snapshot — it skips the DOM-tree (type 2) event for that screen rather than sending an oversized or partial payload. All other events (taps, scrolls, inputs, navigation, custom events) keep flowing; only the replay snapshot for that one oversized screen is omitted (a warning is logged in devMode).
Set maxSnapshotBytes: Infinity to disable the guard and always send the full snapshot. Raise it if replays of complex screens are missing; lower it to cap payload size more aggressively.
Deferred Capture
With deferTracking: true (the default), the SDK keeps heavy work off the interaction path:
- DOM snapshots wait for ongoing gestures/animations (
InteractionManager) and serialize in chunks, yielding to the event loop every ~500 components, - gzip compression of large payloads yields a frame before running,
- snapshots and mutation diffs run through an ordered queue, so replay events are never reordered.
Set deferTracking: false to restore fully synchronous capture — useful in tests or when you need a snapshot to complete before the next line of code runs.
Custom gzip (optional)
Large request payloads (>10KB) are gzipped before sending. By default the SDK uses the bundled pure-JS pako — no setup, works in Expo Go. If you want compression to run on a native thread, pass your own gzip function (e.g. backed by a native module) and the SDK will use it instead:
config={{
// Receives the JSON body string, returns gzipped bytes (sync or async).
gzip: (body) => myNativeGzip(body),
}}The SDK falls back to pako, then to sending uncompressed, if gzip is omitted or throws — so this never breaks the request, and adds no native dependency unless you opt in.
Development Mode
Set devMode: true during development to:
- Generate a new visitor ID on each app start (each reload is a new session)
- Enable
console.logoutput for debugging SDK behavior - See SDK initialization, event queuing, and transport logs in Metro
<InsightechProvider
config={{
account: 'YOUR_PROFILE_ID:YOUR_SERVER_ID',
appName: 'MyApp',
devMode: __DEV__, // Auto-enable in development
}}
navigationRef={navigationRef}
/>Advanced Usage
Manual Screen Tracking (Without React Navigation)
If you don't use React Navigation, track screens manually:
const { trackScreen } = useInsightech();
// Call when a screen mounts or becomes visible
useEffect(() => {
trackScreen('ProductDetail', { productId: product.id });
}, []);Force Sending Events
The SDK batches events and sends them automatically. To force-send immediately (e.g., before the app navigates to an external URL or before a critical action):
const { flush } = useInsightech();
const handleExternalLink = async () => {
await flush();
Linking.openURL(url);
};Accessing the SDK Instance Directly
For advanced use cases, access the raw SDK instance:
const { sdk } = useInsightech();
// Track a tap manually
sdk.trackTap({
nodeIndex: 0,
cssPath: '#custom-element',
tag: 'button',
text: 'Custom Button',
elementX: 100,
elementY: 200,
screenX: 100,
screenY: 300,
scrollTop: 0,
scrollLeft: 0,
pageTop: 0,
pageLeft: 0,
name: '',
href: '',
});Offline Support
Events are persisted to AsyncStorage when the app goes to background. If the app is killed before events can be sent, they are restored and sent on the next app launch. This ensures no data loss during:
- OS killing the app in the background
- App crashes
- Brief network outages (events are retried with exponential backoff)
Using Tracked Components Directly
If you prefer not to use the Babel plugin, import tracked components directly:
import {
TrackedPressable,
TrackedTouchableOpacity,
TrackedTouchableHighlight,
TrackedScrollView,
TrackedFlatList,
TrackedSectionList,
TrackedTextInput,
} from '@insightech/react-native';
// Use them as drop-in replacements
<TrackedPressable testID="my-button" onPress={handlePress}>
<Text>Press Me</Text>
</TrackedPressable>
<TrackedFlatList
testID="product-list"
data={products}
renderItem={renderProduct}
/>How It Works
Architecture Overview
Your App
│
├── InsightechProvider (wraps app, captures fiber tree)
│ │
│ ├── Babel plugin rewrites imports at build time
│ │ Pressable → TrackedPressable
│ │ TouchableOpacity → TrackedTouchableOpacity
│ │ TouchableHighlight → TrackedTouchableHighlight
│ │ ScrollView → TrackedScrollView
│ │ FlatList → TrackedFlatList
│ │ SectionList → TrackedSectionList
│ │ TextInput → TrackedTextInput
│ │
│ ├── Tracked wrappers intercept events (taps, scrolls, inputs)
│ │
│ ├── Component tree serialized to synthetic DOM
│ │ View → <div>, Text → <span>, Pressable → <button>, etc.
│ │
│ └── Privacy masking applied on-device
│
└── Events batched → compressed → sent to Insightech backend
↓
Insightech Dashboard (replay, heatmaps, funnels, analytics)Synthetic DOM Tree
React Native has no HTML DOM. The SDK constructs a synthetic DOM by walking the React fiber tree and mapping components to HTML equivalents:
| React Native Component | HTML Element |
|------------------------|-------------|
| View, SafeAreaView, KeyboardAvoidingView | <div> |
| Text | <span> |
| Image, ImageBackground | <img> |
| TextInput | <input> |
| ScrollView | <div> (with overflow styles) |
| FlatList, SectionList | <ul> |
| Pressable, TouchableOpacity, TouchableHighlight | <button> |
| Switch | <input type="checkbox"> |
| Modal | <div> |
Screen URLs
Screens are represented as URLs for backend compatibility:
https://app.insightech.com/rn/{appName}/{screenName}?param=valueFor example, navigating to a ProductDetail screen with { productId: '42' } generates:
https://app.insightech.com/rn/MyApp/ProductDetail?productId=42Event Batching & Delivery
Events are queued and sent based on type:
- Immediate: Pageview, DOM tree, navigation, form submit, app background
- Force send: Clicks, rage taps (flushes entire queue)
- Batched: Scroll, resize, input, mutations (merged into arrays)
- Size-based: Queue flushed when payload exceeds 200KB
Failed requests are retried with exponential backoff (1s, 2s, 4s) up to 3 times. Events are persisted to AsyncStorage on app background and restored on next launch.
Fiber Access & Compatibility
The SDK accesses React's internal fiber properties to serialize the component tree. These are undocumented internals that may change across versions. The following properties are tried in order:
__internalInstanceHandle(Fabric / New Architecture)_internalFiberInstanceHandleDEV(Paper / development builds)_reactInternals(React 18+)_reactInternalInstance(React 17 / legacy)
If fiber access fails, the SDK degrades gracefully — events are still tracked but session replay won't have DOM data. Test replay after major React/RN version upgrades.
Troubleshooting
No data appearing in the dashboard
- Check devMode logs: Set
devMode: trueand look for[Insightech:Transport]logs in Metro. You should seeResponse: 200for successful sends. - Verify account string: Ensure
accountis formatted asprofileId:serverId(e.g.,'080360e96:us-0-api'). - Check date filter: The dashboard defaults to "Yesterday". Switch to "Today" to see current sessions.
Session replay shows empty/broken layout
- Check fiber access: In devMode, look for
[Insightech:Provider] Root fiber captured. If you see "No fiber found", the SDK can't access the component tree. - Verify RN version: Ensure you're on a supported React Native version (0.68+).
- Add testIDs: Elements without
testIDuse positional selectors which are less stable.
Events not being tracked for specific components
- Check Babel plugin: Verify
@insightech/babel-plugin-react-nativeis in yourbabel.config.js. Clear the Metro cache after changes:npx expo start --clearornpx react-native start --reset-cache. - Check exclusions: Make sure the component isn't in the
excludelist, and that its file isn't matched by anexcludeFilesglob. - Custom components: The Babel plugin only instruments direct imports from
react-native. If you wrapPressablein a custom component, add tracking manually usinguseInsightech. - Check provider placement: Tracked components only record when they render inside
<InsightechProvider>— see below.
<InsightechProvider> placement (no data on some screens)
The Babel plugin instruments components app-wide, and every tracked component looks up the SDK from the nearest <InsightechProvider>. If a tracked component renders outside the provider's subtree, tracking is silently disabled for that component only — the app keeps running (the SDK fails open and never throws).
In development (__DEV__) you'll see this warning once:
[Insightech] A tracked component rendered outside <InsightechProvider>;
tracking is disabled for it. Move <InsightechProvider> so it wraps your
entire app (above your navigator/router).Fix: mount <InsightechProvider> at the very top of your app — above your navigator/router (e.g. wrapping the root layout in app/_layout.tsx for Expo Router, or your NavigationContainer) — so every screen and component is a descendant. A correctly-placed provider never triggers this warning.
High memory usage or battery drain
Adjust throttling and batching settings:
config={{
scrollInterval: 300, // Less frequent scroll events
mutationBatchInterval: 500, // Longer mutation batch window
sendRequestSize: 500, // Larger batches, fewer requests
maxQueueSize: 500, // Smaller max queue
}}
scrollIntervalvsscrollEventThrottle:scrollInterval(provider config) controls how often the SDK records a scroll snapshot.scrollEventThrottleis a standard per-component prop controlling how often the native layer delivers scroll events to JS. The tracked scroll wrappers default it to16(~60fps) only when you don't set it; any value you pass is respected. Scroll heatmaps are built from throttled position snapshots, so a higherscrollEventThrottlefor performance has negligible visual impact.
Virtualized lists (FlatList / SectionList)
Virtualized lists only render the rows currently near the viewport — off-screen rows are unmounted. This has two implications for capture, both expected:
- Replay reflects what was on screen. A DOM-tree snapshot contains the rows mounted at capture time; rows far off-screen aren't in it. As the user scrolls, mutation events capture the rows that come and go, so the replay follows the scroll rather than showing the entire list at once.
- Row tracking stays correct and bounded. As rows recycle, each remounted row gets a fresh node index, and tap/scroll tracking resolves elements by
testIDto their current index — so interactions map to the right row. The internaltestID → indexregistry is LRU-bounded (maxTestIdEntries, default5000), so a long scrolling session won't leak memory; actively-visible elements are refreshed and never evicted.
Recommendations for large lists:
- Give rows a stable
testID(and a stablekeyExtractor) so recycled rows map back consistently. - Tune
windowSize/initialNumToRenderon the list if you want more rows captured per snapshot (at the cost of render work). - Raise
maxTestIdEntriesonly if you have more than ~5,000 distinct interactivetestIDs alive at once (rare).
Events lost when app is killed
Events are automatically persisted to AsyncStorage when the app goes to background and restored on next launch. If you're still losing events, ensure @react-native-async-storage/async-storage is properly installed and linked.
API Reference
InsightechProvider
<InsightechProvider
config={InsightechConfig} // Required: SDK configuration
navigationRef={NavigationRef} // Optional: React Navigation container ref
>
{children}
</InsightechProvider>useInsightech()
const {
trackScreen, // (screenName: string, params?: Record<string, unknown>) => void
trackCustomEvent, // (data: Record<string, unknown>) => void
trackFormSubmit, // (info: FormInfo) => void
trackError, // (info: { message: string, type?: string, context?: Record }) => void
flush, // () => Promise<void>
sdk, // InsightechSDK instance (for advanced usage)
} = useInsightech();Tracked Components
All tracked components are drop-in replacements that forward all props and refs:
| Component | Wraps | Tracks |
|-----------|-------|--------|
| TrackedPressable | Pressable | Tap events with position, element text, CSS path |
| TrackedTouchableOpacity | TouchableOpacity | Tap events; preserves the native opacity fade |
| TrackedTouchableHighlight | TouchableHighlight | Tap events; preserves the native underlay highlight |
| TrackedScrollView | ScrollView | Scroll position, content size, throttled |
| TrackedFlatList | FlatList | Scroll position, content size, throttled |
| TrackedSectionList | SectionList | Scroll position, content size, throttled |
| TrackedTextInput | TextInput | Input keystrokes (masked), focus, blur, change |
Event Types
| Type | Name | Trigger |
|------|------|---------|
| 1 | Pageview | Screen mount / navigation |
| 2 | DOM Tree | Component tree snapshot |
| 3 | App Ready | SDK initialization |
| 4 | App Unload | App backgrounding |
| 7 | Click | onPress on tracked pressables |
| 8 | Input | onChangeText on tracked text inputs |
| 9 | Input Change | onEndEditing / blur with final value |
| 12 | Scroll | onScroll on tracked scroll views |
| 13 | Tab Hidden | App goes to background |
| 14 | Tab Visible | App returns to foreground |
| 15 | Resize | Device orientation / dimensions change |
| 16 | DOM Mutation | Component tree diff after interactions |
| 17 | Field Focus | onFocus on tracked text inputs |
| 18 | Field Blur | onBlur on tracked text inputs |
| 19 | URL Change | Screen navigation |
| 20 | Form Submit | trackFormSubmit() call |
| 97 | API Error | HTTP errors on monitored endpoints |
| 98 | Rage Tap | Rapid repeated taps on same element |
| 99 | Custom / Error | trackCustomEvent() or trackError() |
Development
npm install # Install dependencies
npm test # Run tests (124 tests)
npm test -- --coverage # Run with coverage report
npm run build # Build CJS, ESM, and TypeScript definitions
cd example && npx expo start --ios # Run example appVersion
SDK version: rn-1.1.0
