The availability of features may depend on your plan type. Contact your Customer Success Manager if you have any questions.
Dev guideRecipesAPI ReferenceChangelog
Dev guideAPI ReferenceRecipesChangelogUser GuideGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

Implement a user profile service for the React Native SDK

How to configure a custom User Profile Service for the Optimizely Feature Experimentation React Native SDK.

Use a User Profile Service to persist information about your users and ensure variation assignments are sticky. Sticky implies that when a user gets a particular variation, their assignment will not change.

In the React Native SDK, there is no default implementation. Implementing a User Profile Service is optional and is only necessary if you want to keep variation assignments sticky even when experiment conditions are changed while it is running (for example, audiences, attributes, variation pausing, and traffic distribution). Otherwise, the React Native SDK is stateless and relies on deterministic bucketing to return consistent assignments.

If the User Profile Service does not bucket a user as you expect, then check whether other flags are overriding the bucketing. For information, see How bucketing works.

Implement a service (sync)

To implement a custom User Profile Service, the service needs to expose two functions with the following signatures:

  • lookup – Takes a user ID string and returns a user profile matching the schema below.
  • save – Takes a user profile and persists it.

If you want to use the User Profile Service purely for tracking purposes and not sticky bucketing, you can implement only the save method (always return null from lookup).

Pass the synchronous service to createInstance using the userProfileService option:

import {
  createInstance,
  createPollingProjectConfigManager,
  createBatchEventProcessor,
} from '@optimizely/react-sdk';

const userProfileService = {
  lookup: (userId) => {
    // Perform user profile lookup (e.g., from AsyncStorage or MMKV)
  },
  save: (userProfileMap) => {
    // Persist user profile (e.g., to AsyncStorage or MMKV)
  },
};

const optimizely = createInstance({
  projectConfigManager: createPollingProjectConfigManager({
    sdkKey: 'YOUR_SDK_KEY',
  }),
  eventProcessor: createBatchEventProcessor(),
  userProfileService,
});

Implement a service (async)

The React Native SDK v4 also supports an asynchronous User Profile Service for use with backends like remote databases, AsyncStorage, or any storage that requires async I/O.

🚧

Important

If you use an asynchronous User Profile Service (UserProfileServiceAsync), you must use the async decide hooks (useDecideAsync, useDecideForKeysAsync, useDecideAllAsync). The standard synchronous decide hooks do not call the async User Profile Service.

The async service implements the following interface:

export interface UserProfileServiceAsync {
  lookup(userId: string): Promise<UserProfile>;
  save(profile: UserProfile): Promise<void>;
}

Pass the async service to createInstance using the userProfileServiceAsync option:

import {
  createInstance,
  createPollingProjectConfigManager,
  createBatchEventProcessor,
} from '@optimizely/react-sdk';

const userProfileServiceAsync = {
  lookup: async (userId) => {
    // Fetch user profile from your backend (e.g., remote database, AsyncStorage)
    const response = await fetch(`/api/user-profiles/${userId}`);
    if (!response.ok) return { user_id: userId, experiment_bucket_map: {} };
    return response.json();
  },
  save: async (userProfileMap) => {
    // Persist user profile to your backend
    await fetch(`/api/user-profiles/${userProfileMap.user_id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(userProfileMap),
    });
  },
};

const optimizely = createInstance({
  projectConfigManager: createPollingProjectConfigManager({
    sdkKey: 'YOUR_SDK_KEY',
  }),
  eventProcessor: createBatchEventProcessor(),
  userProfileServiceAsync,
});

Then use the async decide hooks in your components:

import { useDecideAsync } from '@optimizely/react-sdk';

function MyComponent() {
  const { decision, isLoading, error } = useDecideAsync('my-flag');

  if (isLoading) return <Loading />;
  if (error) return <ErrorDisplay error={error} />;

  return decision.enabled ? <NewFeature /> : <Default />;
}

For a more detailed async UPS example, see Implement a user profile service for the JavaScript SDK.

User profile schema

The following code example shows the JSON schema for the user profile object.

Use experiment_bucket_map to override the default bucketing behavior and define an alternate experiment variation for a given user. For each experiment that you want to override, add an object to the map. Use the experiment ID as the key and include a variation_id property that specifies the desired variation. If there isn't an entry for an experiment, then the default bucketing behavior persists.

In the following example, ^[a-zA-Z0-9]+$ is the experiment ID.

{
  "title": "UserProfile",
  "type": "object",
  "properties": {
    "user_id": {"type": "string"},
    "experiment_bucket_map": {"type": "object",
                              "patternProperties": {
                                 "^[a-zA-Z0-9]+$": {"type": "object",
                                                    "properties": {"variation_id": {"type":"string"}},
                                                    "required": ["variation_id"]}
                               }
                             }
  },
  "required": ["user_id", "experiment_bucket_map"]
}

The React Native SDK uses the User Profile Service you provide to override the default bucketing behavior in cases when an experiment assignment has been saved.

When implementing your own User Profile Service, you should load the user profiles into the User Profile Service on initialization and avoid performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the service.