Supaship React Native SDK — feature flags on iOS and Android
@supashiphq/react-native-sdk mirrors the hooks and provider model of @supashiphq/react-sdk for React Native apps. Feature evaluation uses the same SupaClient stack as @supashiphq/javascript-sdk under the hood.
Package: npm @supashiphq/react-native-sdk · Monorepo: javascript-sdk — packages/react-native
Requirements
- React ≥ 16.8 (hooks)
- React Native ≥ 0.71
- Client SDK key for the environment you deploy to (never expose a server key in mobile bundles)
- Peer deps:
react,react-native(the package pulls in@supashiphq/javascript-sdkand@supashiphq/react-core)
Installation
pnpm add @supashiphq/react-native-sdkQuick start
Set toolbar: false in native apps — the dev toolbar is for browsers only.
import {
SupaProvider,
useFeature,
FeaturesWithFallbacks,
} from '@supashiphq/react-native-sdk'
import { View, Text } from 'react-native'
const features = {
'new-header': false,
'theme-config': { mode: 'dark' as const, showLogo: true },
} satisfies FeaturesWithFallbacks
export function App() {
return (
<SupaProvider
config={{
sdkKey: 'your-sdk-key',
environment: 'production',
features,
context: { userId: '123' },
toolbar: false,
}}>
<Home />
</SupaProvider>
)
}
function Home() {
const { feature: newHeader, isLoading } = useFeature('new-header')
if (isLoading) return <Text>Loading…</Text>
return <View>{newHeader ? <Text>New</Text> : <Text>Old</Text>}</View>
}Type-safe feature flags
Define flags once, then augment this package so useFeature / useFeatures stay typed:
// features.ts
import { FeaturesWithFallbacks, InferFeatures } from '@supashiphq/react-native-sdk'
export const FEATURE_FLAGS = {
'new-header': false,
'theme-config': { mode: 'dark' as const, showLogo: true },
} satisfies FeaturesWithFallbacks
declare module '@supashiphq/react-native-sdk' {
interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}Invalid feature keys become TypeScript errors after augmentation.
Use cases
Batch flags with useFeatures
import { useFeatures } from '@supashiphq/react-native-sdk'
function Dashboard() {
const { features, isLoading } = useFeatures(
['new-header', 'paywall-v2'] as const,
{ context: { plan: 'pro' } },
)
if (isLoading) return <Text>…</Text>
return <Text>{features['paywall-v2'] ? 'Paywall B' : 'Paywall A'}</Text>
}Declarative gate — SupaFeature
import { SupaFeature } from '@supashiphq/react-native-sdk'
import { Text } from 'react-native'
function Header() {
return (
<SupaFeature
feature='new-header'
loading={<Text>…</Text>}
variations={{
true: <Text>New header</Text>,
false: <Text>Legacy header</Text>,
}}
/>
)
}Use normal React Native primitives (View, Text, etc.) as children.
Update context after login — useFeatureContext
import { useFeatureContext } from '@supashiphq/react-native-sdk'
import { useEffect } from 'react'
function SessionBridge({ userId }: { userId: string | undefined }) {
const { updateContext } = useFeatureContext()
useEffect(() => {
if (userId) updateContext({ userId })
}, [userId, updateContext])
return null
}mergeWithExisting defaults to true, same as the web React SDK.
Advanced — useClient and useQuery
import { useClient, useQuery } from '@supashiphq/react-native-sdk'
function RemoteConfig() {
const client = useClient()
const { data, isLoading } = useQuery(['remote-config'], () =>
client.getFeature('theme-config').then(theme => ({ theme })),
)
if (isLoading || !data) return null
return <Text>{JSON.stringify(data.theme)}</Text>
}The option refetchOnWindowFocus keeps its name for API parity with the web SDK; on native it refetches when the app returns to active (see below).
refetchOnWindowFocus and AppState
| Platform | When refetchOnWindowFocus is true |
|---|---|
Web (@supashiphq/react-sdk) | visibilitychange / window focus |
| React Native | AppState transitions to active |
Defaults for useFeature / useFeatures limit network traffic unless you opt in.
Sensitive context hashing
If you set sensitiveContextProperties, hashing prefers Web Crypto where available. Some Hermes setups may need a crypto.subtle polyfill; see @supashiphq/javascript-sdk documentation for details.
Unit testing (Jest)
Same pattern as the React SDK: wrap tests with SupaProvider, toolbar={false}, then render your screen.
If Jest resolves react-native in a way that pulls native binaries, map it to a small stub:
// jest.config.js
module.exports = {
moduleNameMapper: {
'^react-native$': '<rootDir>/test/react-native-stub.js',
},
}// test/react-native-stub.js
exports.AppState = {
addEventListener() {
return { remove() {} }
},
}Related docs
- React SDK — same hooks and patterns in the browser
- JavaScript SDK — shared client behavior and network config