React Native SDK

View as Markdown

The Unleash React Native SDK lets you evaluate feature flags in your React Native and Expo applications. It wraps the React SDK and handles two React Native-specific requirements automatically: persistent storage using AsyncStorage instead of localStorage, and a polyfill for crypto.getRandomValues.

You can use this SDK with Unleash Enterprise or Unleash Open Source.

Requirements

  • React Native 0.81 or later (or equivalent Expo SDK version)
  • React 18 or later

Installation

Install the SDK and its required peer dependency, unleash-proxy-client.

$npm install @unleash/unleash-react-native-sdk unleash-proxy-client

Configuration

The following options are available when initializing the SDK. Pass them as the config prop on FlagProvider.

url
stringRequired

The /api/frontend endpoint of your Unleash instance (https://<your-unleash-instance>/api/frontend) or Unleash Edge instance (https://<your-edge-instance>/api/frontend).

clientKey
stringRequired

A frontend token from your Unleash instance.

appName
stringRequired

The name of your application. Used in metrics, included in the Unleash context, and used as the prefix for local cache keys.

refreshInterval
numberDefaults to 30

How often (in seconds) the SDK polls for updated flag configuration. Set to 0 to disable polling after the initial fetch.

disableRefresh
booleanDefaults to false

If true, the SDK does not poll for updates after the initial fetch.

metricsInterval
numberDefaults to 60

How often (in seconds) the SDK sends usage metrics to Unleash.

disableMetrics
booleanDefaults to false

If true, the SDK does not send usage metrics.

storageProvider
IStorageProviderDefaults to AsyncStorageProvider

The storage backend for cached flag configuration. Defaults to AsyncStorageProvider, which uses @react-native-async-storage/async-storage. You can provide a custom implementation that satisfies the IStorageProvider interface.

context
objectDefaults to {}

The initial Unleash context. The SDK automatically sets appName, environment, and a persistent sessionId on the context, so gradual rollouts and session-based targeting work without additional configuration.

bootstrap
arrayDefaults to []

Initial flag configuration to use before the SDK connects to Unleash. See bootstrap flag data.

bootstrapOverride
booleanDefaults to true

If true, bootstrap data overrides any cached flag configuration. If false, bootstrap data is only used when the cache is empty.

headerName
stringDefaults to Authorization

The header name used to send the clientKey to Unleash.

customHeaders
objectDefaults to {}

Additional HTTP headers to include in all requests to Unleash.

impressionDataAll
booleanDefaults to false

If true, the SDK emits impression events for all isEnabled and getVariant calls, including flags that are disabled or non-existent.

Initialization

Wrap your application in FlagProvider in your entry point file. All components inside the provider can access feature flags through hooks.

1import { FlagProvider } from '@unleash/unleash-react-native-sdk';
2
3const config = {
4 url: 'https://<your-unleash-instance>/api/frontend',
5 clientKey: '<your-frontend-token>',
6 appName: 'my-app',
7};
8
9export default function App() {
10 return (
11 <FlagProvider config={config}>
12 <MyApp />
13 </FlagProvider>
14 );
15}

Connection options

Frontend SDKs communicate with Unleash or Unleash Edge through the Frontend API and require a frontend token. Unlike backend SDKs, frontend SDKs do not perform the flag evaluation locally. Instead, they fetch all enabled feature flags for a given Unleash context. The flag evaluation happens either in Unleash Edge, or in the Unleash server, when using the Frontend API directly.

Set url to the /api/frontend endpoint of either your Unleash instance (https://<your-unleash-instance>/api/frontend) or your Edge instance (https://<your-edge-instance>/api/frontend). The URL pattern is the same in both cases. Use Unleash Edge when you need lower latency or higher availability.

Set clientKey to a frontend token. See API tokens for how to generate one.

Check if the SDK is ready

The SDK returns default values (false for useFlag, { name: "disabled", enabled: false } for useVariant) until it loads flag data from one of these sources, checked in this order:

  1. Bootstrap data: if provided and bootstrapOverride is true (or the cache is empty), the SDK uses bootstrap values immediately and ready fires before the first network request.
  2. Cached data: on subsequent launches, the SDK loads previously cached flag data from AsyncStorage. Flags are available, but ready does not fire until the SDK also completes a network request.
  3. Network fetch: the SDK fetches flag configuration from Unleash. The ready event fires on the first successful response (if it has not already fired from bootstrap).

Use the useFlagsStatus hook to check whether the SDK has completed its first network fetch:

1import { useFlagsStatus } from '@unleash/unleash-react-native-sdk';
2
3function MyApp() {
4 const { flagsReady, flagsError } = useFlagsStatus();
5
6 if (flagsError) {
7 return <FallbackView />;
8 }
9
10 if (!flagsReady) {
11 return <LoadingSpinner />;
12 }
13
14 return <MainContent />;
15}

Because flagsReady requires a successful network request, there is a brief loading state on every app launch when you gate rendering on flagsReady, even if cached data exists from a previous session.

If you need instant startup without a loading state, use bootstrapping to provide initial flag values. The SDK uses bootstrap data immediately and fetches the latest flag data from Unleash in the background.

Defer client start

By default, FlagProvider starts polling immediately when the component mounts. To defer this, pass a pre-created client with startClient set to false and call start() when you are ready:

1import { FlagProvider, UnleashClient } from '@unleash/unleash-react-native-sdk';
2import { useEffect } from 'react';
3
4const client = new UnleashClient({
5 url: 'https://<your-unleash-instance>/api/frontend',
6 clientKey: '<your-frontend-token>',
7 appName: 'my-app',
8});
9
10function MyApp() {
11 useEffect(() => {
12 async function init() {
13 await doSetup();
14 client.start();
15 }
16 init();
17 }, []);
18
19 return (
20 <FlagProvider unleashClient={client} startClient={false}>
21 <App />
22 </FlagProvider>
23 );
24}

This pattern is also useful when you need to attach event listeners before the SDK starts.

Check flags

Check if a flag is enabled

Use the useFlag hook to check whether a feature flag is enabled:

1import { useFlag } from '@unleash/unleash-react-native-sdk';
2
3function MyComponent() {
4 const isEnabled = useFlag('my-feature');
5
6 return isEnabled ? <NewFeature /> : <OldFeature />;
7}

If the SDK has not yet loaded flag configuration, useFlag returns false. The component re-renders automatically when the SDK receives flag data.

Check a flag’s variant

Use the useVariant hook to get the variant assigned to a feature flag:

1import { useVariant } from '@unleash/unleash-react-native-sdk';
2
3function MyComponent() {
4 const variant = useVariant('my-experiment');
5
6 if (variant.name === 'blue') {
7 return <BlueVariant />;
8 } else if (variant.name === 'green') {
9 return <GreenVariant />;
10 }
11 return <DefaultVariant />;
12}

If the flag is disabled or has no variants, useVariant returns { name: "disabled", enabled: false }.

Unleash context

The Unleash context determines how flags are evaluated for a given user or session. You can set context fields at initialization and update them at runtime.

Set context at initialization

Pass the context as part of the config object:

1const config = {
2 url: 'https://<your-unleash-instance>/api/frontend',
3 clientKey: '<your-frontend-token>',
4 appName: 'my-app',
5 context: {
6 userId: 'user-123',
7 properties: {
8 plan: 'enterprise',
9 },
10 },
11};

Update context at runtime

Use the useUnleashContext hook to update the context after initialization, for example when a user logs in:

1import { useUnleashContext, useFlag } from '@unleash/unleash-react-native-sdk';
2import { useEffect } from 'react';
3
4function MyComponent({ userId }) {
5 const updateContext = useUnleashContext();
6 const isEnabled = useFlag('my-feature');
7
8 useEffect(() => {
9 updateContext({ userId });
10 }, [userId]);
11
12 return isEnabled ? <NewFeature /> : <OldFeature />;
13}

updateContext triggers a new flag evaluation request with the updated context. To wait for the updated flags before taking action:

1useEffect(() => {
2 async function run() {
3 await updateContext({ userId });
4 // Flags have been re-evaluated with the new userId
5 }
6 run();
7}, [userId]);

Bootstrap flag data

Bootstrapping lets you provide initial flag values that the SDK uses immediately, before it connects to Unleash. This is useful for guaranteeing a specific flag state at app startup or providing fallback values for offline scenarios.

1const config = {
2 url: 'https://<your-unleash-instance>/api/frontend',
3 clientKey: '<your-frontend-token>',
4 appName: 'my-app',
5 bootstrap: [
6 {
7 name: 'my-feature',
8 enabled: true,
9 variant: {
10 name: 'blue',
11 enabled: true,
12 featureEnabled: true,
13 },
14 },
15 ],
16 bootstrapOverride: false,
17};

When bootstrapOverride is true, bootstrap data replaces any flag configuration already cached in AsyncStorage. When bootstrapOverride is false, bootstrap data is only used when the cache is empty.

Local caching and offline behavior

The SDK caches data in AsyncStorage under two keys, both prefixed with the appName from your config:

  • {appName}:repo: the flag data returned by Unleash.
  • {appName}:sessionId: a persistent session identifier used for [gradual rollouts](/concepts/activation-strategies#gradual-rollout, constraints, and stickiness.

On each app launch, the SDK reads both values from AsyncStorage before making a network request. The session ID is generated once and persists across app restarts, which keeps flag evaluation consistent across sessions.

The flag data cache is updated after every successful network fetch.

What happens when Unleash is unreachable

If the SDK cannot connect to Unleash on startup and no bootstrapped values are configured, useFlag returns false for all flags and useVariant returns { name: "disabled", enabled: false }. The SDK does not have explicit retry logic. It continues to poll on the regular refreshInterval and returns to a healthy state on the next successful response.

If bootstrapped values are configured, the SDK uses those values until it can reach Unleash.

App lifecycle

The SDK does not automatically detect app lifecycle changes. When the app is backgrounded, JavaScript timers pause. When the app returns to the foreground, any overdue poll fires, but the timing is not guaranteed.

To fetch fresh flags every time the app comes to the foreground, use AppState from React Native:

1import { useUnleashClient } from '@unleash/unleash-react-native-sdk';
2import { useEffect } from 'react';
3import { AppState } from 'react-native';
4
5function AppStateRefresh() {
6 const client = useUnleashClient();
7
8 useEffect(() => {
9 const subscription = AppState.addEventListener('change', (nextState) => {
10 if (nextState === 'active') {
11 client.updateToggles();
12 }
13 });
14 return () => subscription.remove();
15 }, [client]);
16
17 return null;
18}

Render <AppStateRefresh /> inside FlagProvider alongside your app’s root component.

Events

Use the useUnleashClient hook to access the underlying client and attach event listeners:

1import { useUnleashClient } from '@unleash/unleash-react-native-sdk';
2import { useEffect } from 'react';
3
4function MyComponent() {
5 const client = useUnleashClient();
6
7 useEffect(() => {
8 const handleReady = () => {
9 console.log('Unleash SDK is ready');
10 };
11 const handleError = (error) => {
12 console.error('Unleash error:', error);
13 };
14
15 client.on('ready', handleReady);
16 client.on('error', handleError);
17
18 return () => {
19 client.off('ready', handleReady);
20 client.off('error', handleError);
21 };
22 }, [client]);
23}
EventDescription
initializedThe SDK finished loading bootstrap or cached data from storage. Fires before ready.
readyThe SDK completed its first successful flag fetch, or bootstrap data was applied.
updateThe SDK received updated flag configuration from the server.
errorA network request or initialization error occurred.
impressionA flag was evaluated with impression data enabled.
recoveredThe SDK recovered from a previous error state.
sentThe SDK successfully sent usage metrics to Unleash.

If you need to capture events that fire during initialization (such as initialized or ready), use the deferred client start pattern to attach listeners before the client starts.

Unit testing

To test components that use feature flags, wrap them in a FlagProvider with bootstrapped values. This avoids network requests during tests and gives you full control over flag state.

1import { render, screen } from '@testing-library/react-native';
2import { FlagProvider } from '@unleash/unleash-react-native-sdk';
3import { MyComponent } from './MyComponent';
4
5const testConfig = {
6 url: 'http://example.com', // not used during tests
7 clientKey: 'test-token',
8 appName: 'test-app',
9 disableRefresh: true,
10 disableMetrics: true,
11 bootstrap: [
12 {
13 name: 'my-feature',
14 enabled: true,
15 variant: {
16 name: 'disabled',
17 enabled: false,
18 feature_enabled: true,
19 },
20 },
21 ],
22};
23
24test('renders the new feature when the flag is enabled', async () => {
25 render(
26 <FlagProvider config={testConfig} startClient={false}>
27 <MyComponent />
28 </FlagProvider>
29 );
30
31 expect(await screen.findByText('New feature')).toBeTruthy();
32});

Set disableRefresh and disableMetrics to true in tests to prevent the SDK from making network requests. Use startClient={false} to prevent polling.

Alternatively, you can mock the hooks directly in your test framework:

1jest.mock('@unleash/unleash-react-native-sdk', () => ({
2 useFlag: jest.fn().mockReturnValue(true),
3 useVariant: jest.fn().mockReturnValue({
4 name: 'blue',
5 enabled: true,
6 featureEnabled: true,
7 }),
8 useFlagsStatus: jest.fn().mockReturnValue({
9 flagsReady: true,
10 flagsError: null,
11 }),
12}));

Troubleshooting

If the SDK does not load flags on startup:

  • Verify that url points to the /api/frontend endpoint of your Unleash instance (https://<your-unleash-instance>/api/frontend) or Edge instance (https://<your-edge-instance>/api/frontend). The URL must include the full path.
  • Verify that clientKey is a valid frontend token. A 401 response means the token is invalid or does not have access to the requested environment.
  • Check that your device or simulator has network access to your Unleash instance.

Use useFlagsStatus to surface connection errors:

1const { flagsReady, flagsError } = useFlagsStatus();
2if (flagsError) {
3 console.log('Unleash connection error:', flagsError);
4}

If useFlag always returns false or useVariant always returns { name: "disabled", enabled: false }:

  • Confirm that flagsReady is true. Until the SDK loads flag data from bootstrap, cached storage, or a network fetch, all hooks return default values.
  • Confirm that the flag is enabled in the correct Unleash environment and that your frontend token has access to that environment and project.
  • Check the Unleash context. If your flag uses a gradual rollout or user-based targeting, the userId or sessionId must match the targeting rules configured in Unleash. If your flag uses custom stickiness, you must set that value in the context, otherwise the flag evaluates to false. Use Playground for testing different context values.

flagsReady requires a successful network fetch before it becomes true, even when cached data exists from a previous session. If you gate rendering on flagsReady, there is a brief loading state on every launch.

To eliminate the loading state, use bootstrapping to provide initial flag values. The SDK uses bootstrap data immediately and fetches the latest flag data from Unleash in the background.