@soasap-com/react-sdk
SOASAP React SDK
Lightweight, production-ready feature flags SDK for React 18+ — lock-free O(1) reads, local-first evaluation with persistent browser cache, real-time SSE updates, reactive hooks, zero runtime dependencies, and graceful offline behavior. Vite, Webpack, Next.js client components, CRA.
Installation
Install from npm. Requires React 18+ as a peer dependency. Supports React 18 and 19+.
npm install @soasap-com/react-sdk react
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-sdk';
function App() {
return (
<SoasapProvider
options={{
apiKey: import.meta.env.VITE_SOASAP_API_KEY!,
preload: true,
}}
>
<CheckoutPage />
</SoasapProvider>
);
}
function CheckoutPage() {
const newCheckout = useSoasapBool('new-checkout');
if (newCheckout) {
return <NewCheckout />;
}
return <LegacyCheckout />;
}
Imperative client (no hooks)
Use createSoasapClient() for sync reads outside React components
— event handlers, utilities, or non-UI code paths.
import { createSoasapClient } from '@soasap-com/react-sdk';
const flags = createSoasapClient({
apiKey: import.meta.env.VITE_SOASAP_API_KEY!,
preload: true,
});
if (flags.getBool('new-checkout')) {
console.log('New checkout enabled');
}
Typed access
Reactive hooks for UI components 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) starts the SSE worker in the background,
loads the browser cache on cold start, and never blocks your app shell render.
Without it, lazy mode defers the network connection until the first flag read.
In lazy mode, the first evaluation uses default values (or cached flags from
localStorage if available) while SSE connects in the background.
// Immediate sync (recommended)
<SoasapProvider options={{ apiKey: '...', preload: true }} />
// Lazy sync — first read falls back to defaults or storage cache
<SoasapProvider options={{ apiKey: '...' }} />
Shared client instance
Create one client per browser tab and pass it to the provider. The provider only auto-closes clients it creates itself.
const flags = createSoasapClient({
apiKey: import.meta.env.VITE_SOASAP_API_KEY!,
preload: true,
});
export function Root() {
return (
<SoasapProvider client={flags}>
<Router />
</SoasapProvider>
);
}
Vite integration
Mount the provider at your app root. Works with Vite, Webpack, CRA, and Next.js client components.
// main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { SoasapProvider } from '@soasap-com/react-sdk';
import { App } from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<SoasapProvider
options={{
apiKey: import.meta.env.VITE_SOASAP_API_KEY,
preload: true,
}}
>
<App />
</SoasapProvider>
</StrictMode>,
);
Error handling & observability
Hook background diagnostics without affecting the hot path.
Sources: Network, Storage, Parser.
import { SoasapErrorSource, createSoasapClient } from '@soasap-com/react-sdk';
createSoasapClient({
apiKey: '...',
onError: (ctx) => {
console.error(
`[${ctx.source}] transient=${ctx.isTransient}`,
ctx.exception.message);
},
});
// SoasapErrorSource.Network | .Storage | .Parser
Production safety & guardrails
- Immutable snapshots — atomic reference swap; readers and hooks never see partial updates
- Memory cap protection (anti-DoS) — 5 MB SSE payload cap; oversized streams are dropped and reset
- Payload validation — root element must be a JSON object
{}; invalid payloads are ignored - Storage debounce — browser writes coalesced at most once every ~2.5 seconds
- Zero runtime dependencies — React is the only peer dependency
- One client per tab — create one client per browser tab or app shell
Getters and hooks never throw (except missing SoasapProvider for hooks).
Offline resiliency
| Scenario | Behavior |
|---|---|
| API unavailable | Uses stale cached flags |
| SSE disconnected | Keeps last known snapshot |
| First visit without cache | Returns default values |
| Invalid payload | Payload ignored |
| Storage quota / failure | In-memory mode continues |
| Persistent network issues | Automatic reconnect with backoff |
Browser storage 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
↑
| (atomic reference swap + subscribe)
[Background] SSE → SseEventParser (5MB cap) → StorageWriteCoalescer → localStorage
Supported environments
- React 18 / 19+
- Modern browsers with
fetch,ReadableStream, andlocalStorage - Vite, Webpack, Next.js (client components), CRA, and similar bundlers