This repository houses the React SDK for use with Optimizely Full Stack and Optimizely Rollouts.
Optimizely Full Stack is A/B testing and feature flag management for product development teams. Experiment in any application. Make every feature on your roadmap an opportunity to learn. Learn more at https://www.optimizely.com/platform/full-stack/, or see the documentation.
Optimizely Rollouts is free feature flags for development teams. Easily roll out and roll back features in any application without code deploys. Mitigate risk for every feature on your roadmap. Learn more at https://www.optimizely.com/rollouts/, or see the documentation.
- Automatic datafile downloading
- User ID + attributes memoization
- Render blocking until datafile is ready via a React API
- Optimizely timeout (only block rendering up to the number of milliseconds you specify)
- Library of React components to use with feature flags and A/B tests
The React SDK is compatible with React 16.3.0 +
import {
createInstance,
OptimizelyProvider,
OptimizelyExperiment,
OptimizelyVariation,
OptimizelyFeature,
} from '@optimizely/react-sdk';
const optimizely = createInstance({
sdkKey: 'your-optimizely-sdk-key',
});
class App extends React.Component {
render() {
return (
<OptimizelyProvider
optimizely={optimizely}
timeout={500}
user={{ id: window.userId, attributes: { plan_type: 'bronze' } }}
>
<OptimizelyExperiment experiment="ab-test">
{variation => <p>got variation {variation}</p>}
</OptimizelyExperiment>
<OptimizelyExperiment experiment="button-color">
<OptimizelyVariation variation="blue">
<BlueButton />
</OptimizelyVariation>
<OptimizelyVariation variation="green">
<GreenButton />
</OptimizelyVariation>
<OptimizelyVariation default>
<DefaultButton />
</OptimizelyVariation>
</OptimizelyExperiment>
<OptimizelyFeature feature="sort-algorithm">
{(isEnabled, variables) => <SearchComponent algorithm={variables.algorithm} />}
</OptimizelyFeature>
</OptimizelyProvider>
);
}
}
npm install @optimizely/react-sdk
The ReactSDKClient
client created via createInstance
is the programmatic API to evaluating features and experiments and tracking events. The ReactSDKClient
is what powers the rest of the ReactSDK internally.
arguments
config : object
Object with SDK configuration parameters. This has the same format as the object passed to thecreateInstance
method of the core@optimizely/javascript-sdk
module. For details on this object, see the following pages from the developer docs:
returns
- A
ReactSDKClient
instance.
import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk';
const optimizely = createInstance({
datafile: window.datafile,
});
Required at the root level. Leverages React’s Context
API to allow access to the ReactSDKClient
to components like <OptimizelyFeature>
and <OptimizelyExperiment>
.
props
optimizely : ReactSDKClient
created fromcreateInstance
user: { id: string; attributes?: { [key: string]: any } } | Promise
User info object -id
andattributes
will be passed to the SDK for every feature flag, A/B test, ortrack
call, or aPromise
for the same kind of objecttimeout : Number
(optional) The amount of time for OptimizelyExperiment and OptimizelyFeature components to rendernull
while waiting for the SDK instance to become ready, before resolving..isServerSide : Boolean
(optional) must passtrue
here for server side renderinguserId : String
(optional) Deprecated, prefer usinguser
instead. Another way to provide user id. Theuser
object prop takes precedence when both are provided.userAttributes : Object
: (optional) Deprecated, prefer usinguser
instead. Another way to provide user attributes. Theuser
object prop takes precedence when both are provided.
Before rendering real content, both the datafile and the user must be available to the SDK.
Synchronous loading is the preferred method to ensure that Optimizely is always ready and doesn't add any delay or asynchronous complexity to your application.
import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk';
const optimizely = createInstance({
datafile: window.datafile,
});
class AppWrapper extends React.Component {
render() {
return (
<OptimizelyProvider optimizely={optimizely} user={{ id: window.userId }}>
<App />
</OptimizelyProvider>
);
}
}
If you don't have the datafile downloaded, the ReactSDKClient
can fetch the datafile for you. However, instead of waiting for the datafile to fetch before you render your app, you can immediately render your app and provide a timeout
option to <OptimizelyProvider optimizely={optimizely} timeout={200}>
. This will block rendering of <OptimizelyExperiment>
and <OptimizelyFeature>
components until the datafile loads or the timeout is up (in this case, variation
is null
and isFeatureEnabled
is false
).
import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk';
const optimizely = createInstance({
sdkKey: 'your-optimizely-sdk-key', // Optimizely environment key
});
class App extends React.Component {
render() {
return (
<OptimizelyProvider
optimizely={optimizely}
timeout={500}
user={{ id: window.userId, attributes: { plan_type: 'bronze' } }}
>
<HomePage />
</OptimizelyProvider>
);
}
}
If user information is synchronously available, it can be provided as the user
object prop, as in prior examples. But, if user information must be fetched asynchronously, the user
prop can be a Promise
for a user
object with the same properties (id
and attributes
):
import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk';
import { fetchUser } from './user';
const optimizely = createInstance({
datafile: window.datafile,
});
const userPromise = fetchUser(); // fetchUser returns a Promise for an object with { id, attributes }
class AppWrapper extends React.Component {
render() {
return (
<OptimizelyProvider optimizely={optimizely} user={userPromise}>
<App />
</OptimizelyProvider>
);
}
}
props
experiment : string
Key of the experimentautoUpdate : boolean
(optional) If true, this component will re-render in response to datafile or user changes. Default:false
.timeout : number
(optional) Rendering timeout as described in theOptimizelyProvider
section. Overrides any timeout set on the ancestorOptimizelyProvider
.overrideUserId : string
(optional) Override the userId for calls toactivate
for this component.overrideAttributes : optimizely.UserAttributes
(optional) Override the user attributes for calls toactivate
for this component.children : React.ReactNode | Function
Content or function returning content to be rendered based on the experiment variation. See usage examples below.
You can use OptimizelyExperiment via a child render function. If the component contains a function as a child, <OptimizelyExperiment>
will call that function, with the result of optimizely.activate(experimentKey)
.
import { OptimizelyExperiment } from '@optimizely/react-sdk';
function ExperimentComponent() {
return (
<OptimizelyExperiment experiment="exp1">
{variation => (variation === 'simple' ? <SimpleComponent /> : <DetailedComponent />)}
</OptimizelyExperiment>
);
}
You can also use the <OptimizelyVariation>
component (see below):
OptimizelyVariation
is used with a parent OptimizelyExperiment
to render different content for different variations.
props
variation : string
Key of variation for which child content should be rendereddefault : boolean
(optional) Whentrue
, child content will be rendered in the default case (null
variation returned from the client)children: React.ReactNode
Content to be rendered for this variation
import { OptimizelyExperiment, OptimizelyVariation } from '@optimizely/react-sdk';
function ExperimentComponent() {
return (
<OptimizelyExperiment experiment="exp1">
<OptimizelyVariation variation="simple">
<SimpleComponent />
</OptimizelyVariation>
<OptimizelyVariation variation="detailed">
<ComplexComponent />
</OptimizelyVariation>
<OptimizelyVariation default>
<SimpleComponent />
</OptimizelyVariation>
</OptimizelyExperiment>
);
}
Note: If you are loading the datafile or the user asynchronously, be sure to include an <OptimizelyVariation default>
component as the render path if the datafile or user fails to load.
props
feature : string
Key of the featureautoUpdate : boolean
(optional) If true, this component will re-render in response to datafile or user changes. Default:false
.timeout : number
(optional) Rendering timeout as described in theOptimizelyProvider
section. Overrides any timeout set on the ancestorOptimizelyProvider
.overrideUserId : string
(optional) Override the userId for calls toisFeatureEnabled
for this component.overrideAttributes : optimizely.UserAttributes
(optional) Override the user attributes for calls toisFeatureEnabled
for this component.children : React.ReactNode | Function
Content or function returning content to be rendered based on the enabled status and variable values of the feature. See usage examples below.
import { OptimizelyFeature } from '@optimizely/react-sdk';
function FeatureComponent() {
return (
<OptimizelyFeature feature="new-login-page">
{isEnabled => <a href={isEnabled ? '/login' : '/login2'}>Login</a>}
</OptimizelyFeature>
);
}
variables
provide additional configuration for a feature and is a feature of Optimizely FullStack. variables
are not available in Optimizely Rollouts.
import { OptimizelyFeature } from '@optimizely/react-sdk';
function FeatureComponent() {
return (
<OptimizelyFeature feature="new-login-page">
{(isEnabled, variables) => <a href={isEnabled ? '/login' : '/login2'}>{variables.loginText}</a>}
</OptimizelyFeature>
);
}
A React Hook to retrieve the variation for an experiment, optionally auto updating that value based on underlying user or datafile changes. This can be useful as an alternative to the <OptimizelyExperiment>
component or to use an experiment inside code that is not explicitly rendered.
arguments
experiment : string
Key of the experimentoptions : Object
autoUpdate : boolean
(optional) If true, this hook will update the variation value in response to datafile or user changes. Default:false
.timeout : number
(optional) Client timeout as described in theOptimizelyProvider
section. Overrides any timeout set on the ancestorOptimizelyProvider
.
overrides : Object
overrideUserId : string
(optional) Override the userId for calls toisFeatureEnabled
for this hook.overrideAttributes : optimizely.UserAttributes
(optional) Override the user attributes for calls toisFeatureEnabled
for this hook.
returns
-
Array
of:variation : string
- Theactivate
return value (variation) for theexperiment
provided.clientReady : boolean
- Whether or not the underlyingReactSDKClient
instance is ready or not.didTimeout : boolean
- Whether or not the underlyingReactSDKClient
became ready within the allowedtimeout
range.
Note:
clientReady
can be true even ifdidTimeout
is also true. This indicates that the client became ready after the timeout period.
import { useEffect } from 'react';
import { useExperiment } from '@optimizely/react-sdk';
function LoginComponent() {
const [variation, clientReady] = useExperiment(
'experiment1',
{ autoUpdate: true },
{
/* (Optional) User overrides */
}
);
useEffect(() => {
document.title = variation ? 'login1' : 'login2';
}, [isEnabled]);
return (
<p>
<a href={variation ? '/login' : '/login2'}>Click to login</a>
</p>
);
}
A React Hook to retrieve the status of a feature flag and its variables. This can be useful as an alternative to the <OptimizelyFeature>
component or to use features & variables inside code that is not explicitly rendered.
arguments
feature : string
Key of the featureoptions : Object
autoUpdate : boolean
(optional) If true, this hook will update the feature and it's variables in response to datafile or user changes. Default:false
.timeout : number
(optional) Client timeout as described in theOptimizelyProvider
section. Overrides any timeout set on the ancestorOptimizelyProvider
.
overrides : Object
overrideUserId : string
(optional) Override the userId for calls toisFeatureEnabled
for this hook.overrideAttributes : optimizely.UserAttributes
(optional) Override the user attributes for calls toisFeatureEnabled
for this hook.
returns
-
Array
of:isFeatureEnabled : boolean
- TheisFeatureEnabled
value for thefeature
provided.variables : VariableValuesObject
- The variable values for thefeature
providedclientReady : boolean
- Whether or not the underlyingReactSDKClient
instance is ready or not.didTimeout : boolean
- Whether or not the underlyingReactSDKClient
became ready within the allowedtimeout
range.
Note:
clientReady
can be true even ifdidTimeout
is also true. This indicates that the client became ready after the timeout period.
import { useEffect } from 'react';
import { useFeature } from '@optimizely/react-sdk';
function LoginComponent() {
const [isEnabled, variables] = useFeature(
'feature1',
{ autoUpdate: true },
{
/* (Optional) User overrides */
}
);
useEffect(() => {
document.title = isEnabled ? 'login1' : 'login2';
}, [isEnabled]);
return (
<p>
<a href={isEnabled ? '/login' : '/login2'}>{variables.loginText}</a>
</p>
);
}
Any component under the <OptimizelyProvider>
can access the Optimizely ReactSDKClient
via the higher-order component (HoC) withOptimizely
.
arguments
Component : React.Component
Component which will be enhanced with the following props:optimizely : ReactSDKClient
The client object which was passed to theOptimizelyProvider
optimizelyReadyTimeout : number | undefined
The timeout which was passed to theOptimizelyProvider
isServerSide : boolean
Value that was passed to theOptimizelyProvider
returns
- A wrapped component with additional props as described above
import { withOptimizely } from '@optimizely/react-sdk';
class MyComp extends React.Component {
constructor(props) {
super(props);
const { optimizely } = this.props;
const isFeat1Enabled = optimizely.isFeatureEnabled('feat1');
const feat1Variables = optimizely.getFeatureVariables('feat1');
this.state = {
isFeat1Enabled,
feat1Variables,
};
}
render() {}
}
const WrappedMyComponent = withOptimizely(MyComp);
Note: The optimizely
client object provided via withOptimizely
is automatically associated with the user
prop passed to the ancestor OptimizelyProvider
- the id
and attributes
from that user
object will be automatically forwarded to all appropriate SDK method calls. So, there is no need to pass the userId
or attributes
arguments when calling methods of the optimizely
client object, unless you wish to use different userId
or attributes
than those given to OptimizelyProvider
.
Use the withOptimizely
HoC for tracking.
import { withOptimizely } from '@optimizely/react-sdk';
class SignupButton extends React.Component {
onClick = () => {
const { optimizely } = this.props;
optimizely.track('signup-clicked');
// rest of click handler
};
render() {
<button onClick={this.onClick}>Signup</button>;
}
}
const WrappedSignupButton = withOptimizely(SignupButton);
Note: As mentioned above, the optimizely
client object provided via withOptimizely
is automatically associated with the user
prop passed to the ancestor OptimizelyProvider.
There is no need to pass userId
or attributes
arguments when calling track
, unless you wish to use different userId
or attributes
than those given to OptimizelyProvider
.
The following type definitions are used in the ReactSDKClient
interface:
UserAttributes : { [name: string]: any }
User : { id: string | null, attributes: userAttributes }
VariableValuesObject : { [key: string]: boolean | number | string | null }
EventTags : { [key: string]: string | number | boolean; }
ReactSDKClient
instances have the methods/properties listed below. Note that in general, the API largely matches that of the core @optimizely/optimizely-sdk
client instance, which is documented on the Optimizely X Full Stack developer docs site. The major exception is that, for most methods, user id & attributes are optional arguments. ReactSDKClient
has a current user. This user's id & attributes are automatically applied to all method calls, and overrides can be provided as arguments to these method calls if desired.
onReady(opts?: { timeout?: number }): Promise<onReadyResult>
Returns a Promise that fulfills with anonReadyResult
object representing the initialization process. The instance is ready when it has fetched a datafile and a user is available (viasetUser
being called with an object, or a Promise passed tosetUser
becoming fulfilled). If thetimeout
period happens before the client instance is ready, theonReadyResult
object will contain an additional key,dataReadyPromise
, which can be used to determine when, if ever, the instance does become ready.user: User
The current user associated with this client instancesetUser(userInfo: User | Promise<User>): void
Call this to update the current useronUserUpdate(handler: (userInfo: User) => void): () => void
Subscribe a callback to be called when this instance's current user changes. Returns a function that will unsubscribe the callback.activate(experimentKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): string | null
Activate an experiment, and return the variation for the given user.getVariation(experimentKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): string | null
Return the variation for the given experiment and user.getFeatureVariables(featureKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): VariableValuesObject
: Decide and return variable values for the given feature and user
Warning: Deprecated since 2.1.0
getAllFeatureVariables
is added in JavaScript SDK which is similarly returning all the feature variables, but it sends only single notification of typeall-feature-variables
instead of sending for each variable. AsgetFeatureVariables
was added when this functionality wasn't provided byJavaScript SDK
, so there is no need of it now and it would be removed in next major releasegetFeatureVariableString(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: optimizely.UserAttributes): string | null
: Decide and return the variable value for the given feature, variable, and usergetFeatureVariableInteger(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): number | null
Decide and return the variable value for the given feature, variable, and usergetFeatureVariableBoolean(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): boolean | null
Decide and return the variable value for the given feature, variable, and usergetFeatureVariableDouble(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): number | null
Decide and return the variable value for the given feature, variable, and userisFeatureEnabled(featureKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): boolean
Return the enabled status for the given feature and usergetEnabledFeatures(overrideUserId?: string, overrideAttributes?: UserAttributes): Array<string>
: Return the keys of all features enabled for the given usertrack(eventKey: string, overrideUserId?: string | EventTags, overrideAttributes?: UserAttributes, eventTags?: EventTags): void
Track an event to the Optimizely results backendsetForcedVariation(experiment: string, overrideUserIdOrVariationKey: string, variationKey?: string | null): boolean
Set a forced variation for the given experiment, variation, and user. Note: callingsetForcedVariation
on a given client will trigger a re-render of alluseExperiment
hooks andOptimizelyExperiment
components that are using that client.getForcedVariation(experiment: string, overrideUserId?: string): string | null
Get the forced variation for the given experiment, variation, and user
To rollout or experiment on a feature by user rather than by random percentage, you will use Attributes and Audiences. To do this, follow the documentation on how to run a beta using the React code samples.
Right now server side rendering is possible with a few caveats.
Caveats
-
You must download the datafile manually and pass in via the
datafile
option. Can not usesdkKey
to automatically download. -
Rendering of components must be completely synchronous (this is true for all server side rendering), thus the Optimizely SDK assumes that the optimizely client has been instantiated and fired it's
onReady
event already.
Similar to browser side rendering you will need to wrap your app (or portion of the app using Optimizely) in the <OptimizelyProvider>
component. A new prop
isServerSide
must be equal to true.
<OptimizelyProvider optimizely={optimizely} user={{ id: 'user1' }} isServerSide={true}>
<App />
</OptimizelyProvider>
All other Optimizely components, such as <OptimizelyFeature>
and <OptimizelyExperiment>
can remain the same.
import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';
import {
createInstance,
OptimizelyProvider,
OptimizelyFeature,
OptimizelyExperiment,
OptimizelyVariation,
} from '@optimizely/react-sdk';
const fetch = require('node-fetch');
async function main() {
const resp = await fetch('https://cdn.optimizely.com/datafiles/<Your-SDK-Key>.json');
const datafile = await resp.json();
const optimizely = createInstance({
datafile,
});
const output = ReactDOMServer.renderToString(
<OptimizelyProvider optimizely={optimizely} user={{ id: 'user1' }} isServerSide={true}>
<OptimizelyFeature feature="feature1">
{featureEnabled => (featureEnabled ? <p>enabled</p> : <p>disabled</p>)}
</OptimizelyFeature>
<OptimizelyExperiment experiment="abtest1">
<OptimizelyVariation variation="var1">
<p>variation 1</p>
</OptimizelyVariation>
<OptimizelyVariation default>
<p>default variation</p>
</OptimizelyVariation>
</OptimizelyExperiment>
</OptimizelyProvider>
);
console.log('output', output);
}
main();
To disable sending all events to Optimizely's results backend, use the logOnlyEventDispatcher
when creating a client:
import { createInstance, logOnlyEventDispatcher } from '@optimizely/react-sdk';
const optimizely = createInstance({
datafile: window.datafile,
eventDispatcher: logOnlyEventDispatcher,
});
First-party code subject to copyrights held by Optimizely, Inc. and its contributors and licensed to you under the terms of the Apache 2.0 license.
This repository includes the following third party open source code:
hoist-non-react-statics Copyright © 2015 Yahoo!, Inc. License: BSD
js-tokens Copyright © 2014, 2015, 2016, 2017, 2018, 2019 Simon Lydell License: MIT
json-schema Copyright © 2005-2015, The Dojo Foundation License: BSD
lodash Copyright © JS Foundation and other contributors License: MIT
loose-envify Copyright © 2015 Andres Suarez [email protected] License: MIT
node-murmurhash Copyright © 2012 Gary Court, Derek Perez License: MIT
object-assign Copyright © Sindre Sorhus (sindresorhus.com) License: MIT
promise-polyfill Copyright © 2014 Taylor Hakes Copyright © 2014 Forbes Lindesay License: MIT
prop-types Copyright © 2013-present, Facebook, Inc. License: MIT
react-is Copyright © Facebook, Inc. and its affiliates. License: MIT
react Copyright © Facebook, Inc. and its affiliates. License: MIT
scheduler Copyright © Facebook, Inc. and its affiliates. License: MIT
utility-types Copyright © 2016 Piotr Witek [email protected] License: MIT
node-uuid Copyright © 2010-2016 Robert Kieffer and other contributors License: MIT
To regenerate the dependencies use by this package, run the following command:
npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)'
Please see CONTRIBUTING for more information.