Skip to main content

React Native SDK

The Syntropy React Native SDK captures errors from React Native and Expo applications.

npm install @eclosion-tech/syntropy-react-native

Expo Setup

Initialize as early as possible in your app, before other imports:

// App.tsx - at the very top
import { Syntropy } from '@eclosion-tech/syntropy-react-native';
import * as Application from 'expo-application';
import * as Updates from 'expo-updates';

Syntropy.init({
projectId: 'your-project-id',

// Release tracking
release: Application.nativeApplicationVersion ?? '1.0.0',
dist: Application.nativeBuildVersion ?? undefined,
environment: __DEV__ ? 'development' : 'production',

// OTA updates (Expo)
updateId: Updates.updateId ?? undefined,
updateChannel: Updates.channel ?? undefined,
});

// Rest of your imports
import { StatusBar } from 'expo-status-bar';
import { NavigationContainer } from '@react-navigation/native';
// ...

Bare React Native Setup

// index.js - at the very top
import { Syntropy } from '@eclosion-tech/syntropy-react-native';
import { version } from './package.json';

Syntropy.init({
projectId: 'your-project-id',
release: version,
environment: __DEV__ ? 'development' : 'production',
});

import { AppRegistry } from 'react-native';
import App from './App';
// ...

Configuration Options

Syntropy.init({
// Required
projectId: 'your-project-id',
dsn: 'https://syntropy.chat/api/ingest', // Optional endpoint override

// Release tracking
release: '1.0.0',
dist: '42',
environment: 'production',
updateId: 'expo-update-id', // OTA update ID
updateChannel: 'production', // OTA channel

// Behavior
enabled: true,
autoCapture: true, // Capture unhandled errors
breadcrumbs: true, // Collect breadcrumbs
maxBreadcrumbs: 50,

// Performance
flushInterval: 5000,
maxBatchSize: 20,
sampleRate: 1.0,

// Filtering
beforeSend: (event) => event, // Return null to drop

// Debugging
debug: false,
});

Automatic Error Capture

With autoCapture: true, the SDK captures:

  • JavaScript errors via React Native's ErrorUtils
  • Unhandled Promise rejections
  • Native crashes (requires additional setup)

Manual Error Capture

import { Syntropy } from '@eclosion-tech/syntropy-react-native';

try {
await riskyAsyncOperation();
} catch (error) {
Syntropy.captureError(error, {
tags: { screen: 'Profile' },
extra: { userId: user.id },
});
}

React Navigation

import { NavigationContainer } from '@react-navigation/native';
import { Syntropy } from '@eclosion-tech/syntropy-react-native';

function App() {
const routeNameRef = useRef<string>();
const navigationRef = useNavigationContainerRef();

return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
routeNameRef.current = navigationRef.getCurrentRoute()?.name;
}}
onStateChange={() => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.getCurrentRoute()?.name;

if (previousRouteName !== currentRouteName && currentRouteName) {
Syntropy.addBreadcrumb({
category: 'navigation',
message: `Navigate to ${currentRouteName}`,
data: { from: previousRouteName, to: currentRouteName },
});
}

routeNameRef.current = currentRouteName;
}}
>
{/* ... */}
</NavigationContainer>
);
}

App State Breadcrumbs

Track when the app goes to background/foreground:

import { AppState, AppStateStatus } from 'react-native';
import { Syntropy } from '@eclosion-tech/syntropy-react-native';

useEffect(() => {
const subscription = AppState.addEventListener('change', (state: AppStateStatus) => {
Syntropy.addBreadcrumb({
category: 'custom',
message: `App state: ${state}`,
data: { state },
});
});

return () => subscription.remove();
}, []);

User Identification

// After login
Syntropy.identify({
id: user.id,
email: user.email,
name: user.displayName,
});

// After logout
Syntropy.reset();

OTA Update Tracking

For Expo apps using EAS Update or classic updates:

import * as Updates from 'expo-updates';

Syntropy.init({
projectId: 'your-project-id',
release: Application.nativeApplicationVersion ?? '1.0.0',
updateId: Updates.updateId ?? undefined,
updateChannel: Updates.channel ?? undefined,
});

This allows you to see if an error was introduced by:

  • A native build update (new release)
  • An OTA JavaScript update (new updateId)

Error Boundaries

Wrap your app in an error boundary:

import { Syntropy } from '@eclosion-tech/syntropy-react-native';

class ErrorBoundary extends React.Component {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
Syntropy.captureError(error, {
extra: { componentStack: errorInfo.componentStack },
});
}

render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}

Debugging

Enable debug mode to see SDK activity:

Syntropy.init({
projectId: 'your-project-id',
debug: __DEV__, // Only in development
});

This logs all SDK activity to the console.

Conversation Events

Syntropy.captureConversation('conversation_started', {
conversationId: 'conv_123',
channel: 'mobile_chat',
});

Customer APIs from React Native (Public Client)

Use createSyntropyPublicClient with a publishable key (syn_pk_) for client-side customer operations. Publishable keys are safe to embed in your app -- they only allow identify, track, and device registration.

import { createSyntropyPublicClient } from '@eclosion-tech/syntropy-react-native';

const customers = createSyntropyPublicClient({
accountId: 'your_project_uuid',
publishableKey: 'syn_pk_xxx',
baseUrl: 'https://syntropy.chat/api',
});

// Identify (upsert) a customer
await customers.identify({
externalId: 'user_123',
email: 'user@example.com',
metadata: { source: 'mobile' },
});

// Track an event
await customers.track({
name: 'purchase_completed',
externalId: 'user_123',
payload: { amount: 99.99, currency: 'USD' },
});

// Register a device for push notifications
await customers.registerDevice({
externalId: 'user_123',
platform: 'ios',
pushToken: 'expo_push_token_xxx',
active: true,
});

Create publishable keys in the dashboard under Settings > API Keys by selecting the "Publishable" key type.

Server-Side Customer APIs

For full admin access (listing customers, segments, CSV import, etc.), use createSyntropyApiClient with a secret key (syn_sk_) from your backend:

import { createSyntropyApiClient } from '@eclosion-tech/syntropy-react-native';

const api = createSyntropyApiClient({
baseUrl: 'https://syntropy.chat/api',
apiKey: 'syn_sk_xxx',
});

await api.customers.upsert(
{ organizationId: 'org_uuid', projectId: 'project_uuid' },
{
externalId: 'user_123',
email: 'user@example.com',
metadata: { source: 'mobile' },
}
);

See SDK API Client for full API coverage.

Push Notification Delivery

Once a device is registered, Syntropy can deliver push notifications through workflows. Configure push credentials in your project dashboard under Settings > Push.

Supported Providers

ProviderPlatformToken Format
Apple APNsiOSNative APNs device token (hex string)
Firebase FCMAndroidFCM registration token
Expo PushiOS / AndroidExponentPushToken[...]

Apple APNs Setup

  1. In the Apple Developer portal, create an APNs Authentication Key (.p8 file)
  2. Note your Key ID, Team ID, and app Bundle ID
  3. In the Syntropy dashboard, go to Settings > Push and add an APNs provider
  4. Upload the .p8 file and fill in the Key ID, Team ID, and Bundle ID
  5. Select Production or Sandbox environment

Firebase FCM Setup

  1. In the Firebase Console, go to Project Settings > Service Accounts
  2. Click Generate New Private Key to download a service account JSON file
  3. In the Syntropy dashboard, go to Settings > Push and add an FCM provider
  4. Upload the service account JSON file

Expo Push Setup

  1. In the Syntropy dashboard, go to Settings > Push and add an Expo provider
  2. Optionally provide an Expo access token for higher rate limits
  3. Register devices using ExponentPushToken[...] tokens from expo-notifications

Token Lifecycle

Syntropy automatically manages device token validity:

  • When APNs returns a 410 Gone or Unregistered response, the device is marked inactive
  • When FCM returns UNREGISTERED, the device is marked inactive
  • When Expo returns DeviceNotRegistered, the device is marked inactive
  • Inactive devices are excluded from future push deliveries
  • Devices can be re-activated by calling registerDevice again with a fresh token