Active npm 1.0.3 @soasap-com/react-native-sdk

SOASAP React Native SDK

Lightweight, production-ready feature flags SDK for React Native 0.71+ and Expo — lock-free O(1) reads, local-first evaluation with AsyncStorage-backed cache, real-time SSE updates, reactive hooks, zero runtime dependencies, and graceful offline behavior.

Installation

Install from npm. Requires React 18+, React Native 0.71+, and @react-native-async-storage/async-storage 1.17+ as peer dependencies.

Expo: npx expo install @soasap-com/react-native-sdk @react-native-async-storage/async-storage

npm install @soasap-com/react-native-sdk @react-native-async-storage/async-storage react react-native

Quick start

Wrap your app with SoasapProvider, enable preload: true for non-blocking startup sync, and read flags with hooks that re-render when values change.

import { SoasapProvider, useSoasapBool } from '@soasap-com/react-native-sdk';

function App() {
  return (
    <SoasapProvider
      options={{
        apiKey: process.env.EXPO_PUBLIC_SOASAP_API_KEY!,
        preload: true,
      }}
    >
      <CheckoutScreen />
    </SoasapProvider>
  );
}

function CheckoutScreen() {
  const newCheckout = useSoasapBool('new-checkout');

  if (newCheckout) {
    return <NewCheckout />;
  }
  return <LegacyCheckout />;
}

Imperative client (no hooks)

Use createSoasapClient() for sync reads outside React components — navigation guards, utilities, or non-UI code paths.

import { createSoasapClient } from '@soasap-com/react-native-sdk';

const flags = createSoasapClient({
  apiKey: process.env.EXPO_PUBLIC_SOASAP_API_KEY!,
  preload: true,
});

if (flags.getBool('new-checkout')) {
  console.log('New checkout enabled');
}

Typed access

Reactive hooks for screens and sync getters on the imperative client. JSON keys from the dashboard are returned as stored (typically camelCase).

// Hooks (reactive)
useSoasapBool('feature-x');
useSoasapNumber('rate-limit', 100);
useSoasapString('ui-theme', 'light');
useSoasapJson<CheckoutConfig>('checkout-config');

// Client (sync)
flags.getBool('feature-x');
flags.getNumber('rate-limit', 100);
flags.getString('ui-theme', 'light');
flags.getJson<CheckoutConfig>('checkout-config');

Startup sync

preload: true (recommended) hydrates from AsyncStorage in the background while the SSE worker connects — it never blocks your app shell render. Without it, lazy mode defers the network connection until the first flag read.

Unlike the web SDK (localStorage is synchronous), AsyncStorage.getItem() returns a Promise. On cold start, getBool() and hooks still read in O(1) but may return defaults for ~20–50 ms until the cache hydrates — use waitForStorage or useSoasapStorageReady() to avoid a brief UI flash.

// Immediate sync (recommended)
<SoasapProvider options={{ apiKey: '...', preload: true }} />

// Gate until AsyncStorage is ready
<SoasapProvider
  options={{ apiKey: '...', preload: true }}
  waitForStorage
  fallback={<SplashScreen />}
>
  <App />
</SoasapProvider>

// Lazy sync — first read uses storage cache or defaults
<SoasapProvider options={{ apiKey: '...' }} />

Shared client instance

Create one client per app instance at module scope and pass it to the provider. The provider only auto-closes clients it creates itself.

Do not create the client inside a component without useMemo — Fast Refresh can remount the root and leak SSE connections on every save.

const flags = createSoasapClient({
  apiKey: process.env.EXPO_PUBLIC_SOASAP_API_KEY!,
  preload: true,
});

export function Root() {
  return (
    <SoasapProvider client={flags}>
      <Navigation />
    </SoasapProvider>
  );
}

Expo integration

Mount the provider at your app root. Works with Expo SDK 49+, bare React Native, and React Navigation.

// App.tsx
import { SoasapProvider } from '@soasap-com/react-native-sdk';
import { NavigationContainer } from '@react-navigation/native';
import { RootNavigator } from './navigation';

export default function App() {
  return (
    <SoasapProvider
      options={{
        apiKey: process.env.EXPO_PUBLIC_SOASAP_API_KEY!,
        preload: true,
      }}
    >
      <NavigationContainer>
        <RootNavigator />
      </NavigationContainer>
    </SoasapProvider>
  );
}

Error handling & observability

Hook background diagnostics without affecting the hot path. Sources: Network, Storage, Parser.

import { SoasapErrorSource, createSoasapClient } from '@soasap-com/react-native-sdk';

createSoasapClient({
  apiKey: '...',
  onError: (ctx) => {
    console.error(
      `[${ctx.source}] transient=${ctx.isTransient}`,
      ctx.exception.message);
  },
});

// SoasapErrorSource.Network | .Storage | .Parser

Production safety & guardrails

Getters and hooks never throw (except missing SoasapProvider for hooks).

Offline resiliency

ScenarioBehavior
API unavailableUses stale cached flags
SSE disconnectedKeeps last known snapshot
First visit without cacheReturns default values
Invalid payloadPayload ignored
Storage quota / failureIn-memory mode continues
Persistent network issuesAutomatic reconnect with backoff

AsyncStorage key: soasap:cache:<api-key-prefix>.
Override with cacheKeyPrefix: 'myapp:flags' or a custom storage implementation.

Architecture

[Hot Path]   getBool() / hooks → currentSnapshot ref → O(1) lookup (sync, in-memory)
                              ↑
                              | (atomic reference swap + subscribe)
[Background] AsyncStorage.getItem() → hydrate snapshot (~20–50 ms on cold start)
             SSE → SseEventParser (5MB cap) → StorageWriteCoalescer → AsyncStorage

Supported environments

← All SDKs