Gathering detailed insights and metrics for @optimizely/react-sdk
Gathering detailed insights and metrics for @optimizely/react-sdk
Gathering detailed insights and metrics for @optimizely/react-sdk
Gathering detailed insights and metrics for @optimizely/react-sdk
React SDK for Optimizely Feature Experimentation and Optimizely Full Stack (legacy)
npm install @optimizely/react-sdk
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
90 Stars
169 Commits
36 Forks
61 Watching
34 Branches
251 Contributors
Updated on 28 Nov 2024
TypeScript (97.53%)
JavaScript (2.44%)
Shell (0.02%)
Cumulative downloads
Total Downloads
Last day
-28.4%
15,075
Compared to previous day
Last week
-4.8%
93,109
Compared to previous week
Last month
-3.4%
460,804
Compared to previous month
Last year
-0.4%
4,893,455
Compared to previous year
1
28
This repository houses the React SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy).
Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at Optimizely.com, or see the developer documentation.
Optimizely Rollouts is free feature flags for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap.
Refer to the React SDK's developer documentation for detailed instructions on getting started with using the SDK.
For React Native, review the React Native developer documentation.
The React SDK is compatible with React 16.8.0 +
1import { 2 createInstance, 3 OptimizelyProvider, 4 useDecision, 5} from '@optimizely/react-sdk'; 6 7const optimizelyClient = createInstance({ 8 sdkKey: 'your-optimizely-sdk-key', 9}); 10 11function MyComponent() { 12 const [decision] = useDecision('sort-algorithm'); 13 return ( 14 <React.Fragment> 15 <SearchComponent algorithm={decision.variables.algorithm} /> 16 { decision.variationKey === 'relevant_first' && <RelevantFirstList /> } 17 { decision.variationKey === 'recent_first' && <RecentFirstList /> } 18 </React.Fragment> 19 ); 20} 21 22class App extends React.Component { 23 render() { 24 return ( 25 <OptimizelyProvider 26 optimizely={optimizelyClient} 27 timeout={500} 28 user={{ id: window.userId, attributes: { plan_type: 'bronze' } }} 29 > 30 <MyComponent /> 31 </OptimizelyProvider> 32 ); 33 } 34}
npm install @optimizely/react-sdk
For React Native, installation instruction is bit different. Check out the
createInstance
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 the createInstance
method of the core @optimizely/javascript-sdk
module. For details on this object, see the following pages from the developer docs:
returns
ReactSDKClient
instance.1import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk'; 2 3const optimizely = createInstance({ 4 datafile: window.optimizelyDatafile, 5});
<OptimizelyProvider>
Required at the root level. Leverages React’s Context
API to allow access to the ReactSDKClient
to the useDecision
hook.
props
optimizely : ReactSDKClient
created from createInstance
user: { id: string; attributes?: { [key: string]: any } } | Promise
User info object - id
and attributes
will be passed to the SDK for every feature flag, A/B test, or track
call, or a Promise
for the same kind of objecttimeout : Number
(optional) The amount of time for useDecision
to return null
flag Decision while waiting for the SDK instance to become ready, before resolving.isServerSide : Boolean
(optional) must pass true
here for server side renderinguserId : String
(optional) Deprecated, prefer using user
instead. Another way to provide user id. The user
object prop takes precedence when both are provided.userAttributes : Object
: (optional) Deprecated, prefer using user
instead. Another way to provide user attributes. The user
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. When initializing with both the SDK key and datafile, the SDK will use the given datafile to start, then download the latest version of the datafile in the background.
1import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk'; 2 3const optimizelyClient = createInstance({ 4 datafile: window.optimizelyDatafile, 5 sdkKey: 'your-optimizely-sdk-key', // Optimizely environment key 6}); 7 8class AppWrapper extends React.Component { 9 render() { 10 return ( 11 <OptimizelyProvider optimizely={optimizelyClient} user={{ id: window.userId }}> 12 <App /> 13 </OptimizelyProvider> 14 ); 15 } 16}
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}>
. The useDecision
hook returns isClientReady
and didTimeout
. You can use these to block rendering of component until the datafile loads or the timeout is over.
1import { OptimizelyProvider, createInstance, useDecision } from '@optimizely/react-sdk'; 2 3const optimizelyClient = createInstance({ 4 sdkKey: 'your-optimizely-sdk-key', // Optimizely environment key 5}); 6 7function MyComponent() { 8 const [decision, isClientReady, didTimeout] = useDecision('the-flag'); 9 return ( 10 <React.Fragment> 11 { isClientReady && <div>The Component</div> } 12 { didTimeout && <div>Default Component</div>} 13 { /* If client is not ready and time out has not occured yet, do not render anything */ } 14 </React.Fragment> 15 ); 16} 17 18class App extends React.Component { 19 render() { 20 return ( 21 <OptimizelyProvider 22 optimizely={optimizelyClient} 23 timeout={500} 24 user={{ id: window.userId, attributes: { plan_type: 'bronze' } }} 25 > 26 <MyComponent /> 27 </OptimizelyProvider> 28 ); 29 } 30}
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
):
1import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk'; 2import { fetchUser } from './user'; 3 4const optimizely = createInstance({ 5 datafile: window.optimizelyDatafile, 6}); 7 8const userPromise = fetchUser(); // fetchUser returns a Promise for an object with { id, attributes } 9 10class AppWrapper extends React.Component { 11 render() { 12 return ( 13 <OptimizelyProvider optimizely={optimizely} user={userPromise}> 14 <App /> 15 </OptimizelyProvider> 16 ); 17 } 18}
useDecision
HookA React Hook to retrieve the decision result for a flag key, optionally auto updating that decision based on underlying user or datafile changes.
arguments
flagKey : string
The key of the feature flag.options : Object
autoUpdate : boolean
(optional) If true, this hook will update the flag decision in response to datafile or user changes. Default: false
.timeout : number
(optional) Client timeout as described in the OptimizelyProvider
section. Overrides any timeout set on the ancestor OptimizelyProvider
.decideOption: OptimizelyDecideOption[]
(optional) Array of OptimizelyDecideOption enums.overrides : Object
overrideUserId : string
(optional) Override the userId to be used to obtain the decision result for this hook.overrideAttributes : optimizely.UserAttributes
(optional) Override the user attributes to be used to obtain the decision result for this hook.returns
Array
of:
decision : OptimizelyDecision
- Decision result for the flag key.clientReady : boolean
- Whether or not the underlying ReactSDKClient
instance is ready or not.didTimeout : boolean
- Whether or not the underlying ReactSDKClient
became ready within the allowed timeout
range.Note: clientReady
can be true even if didTimeout
is also true. This indicates that the client became ready after the timeout period.
1import { useEffect } from 'react'; 2import { useDecision } from '@optimizely/react-sdk'; 3 4function LoginComponent() { 5 const [decision, clientReady] = useDecision( 6 'login-flag', 7 { autoUpdate: true }, 8 { 9 /* (Optional) User overrides */ 10 } 11 ); 12 useEffect(() => { 13 document.title = decision.enabled ? 'login-new' : 'login-default'; 14 }, [decision.enabled]); 15 16 return ( 17 <p> 18 <a href={decision.enabled ? '/login-new' : '/login-default'}>Click to login</a> 19 </p> 20 ); 21}
withOptimizely
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 the OptimizelyProvider
optimizelyReadyTimeout : number | undefined
The timeout which was passed to the OptimizelyProvider
isServerSide : boolean
Value that was passed to the OptimizelyProvider
returns
1import { withOptimizely } from '@optimizely/react-sdk'; 2 3class MyComp extends React.Component { 4 constructor(props) { 5 super(props); 6 const { optimizely } = this.props; 7 const decision = optimizely.decide('feat1'); 8 9 this.state = { 10 decision.enabled, 11 decision.variables, 12 }; 13 } 14 15 render() {} 16} 17 18const 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
.
useContext
Any component under the <OptimizelyProvider>
can access the Optimizely ReactSDKClient
via the OptimizelyContext
with useContext
.
arguments
OptimizelyContext : React.Context<OptimizelyContextInterface>
The Optimizely context initialized in a parent component (or App).returns
optimizely : ReactSDKClient
The client object which was passed to the OptimizelyProvider
isServerSide : boolean
Value that was passed to the OptimizelyProvider
timeout : number | undefined
The timeout which was passed to the OptimizelyProvider
1import React, { useContext } from 'react'; 2import { OptimizelyContext } from '@optimizely/react-sdk'; 3 4function MyComponent() { 5 const { optimizely, isServerSide, timeout } = useContext(OptimizelyContext); 6 const decision = optimizely.decide('my-feature'); 7 const onClick = () => { 8 optimizely.track('signup-clicked'); 9 // rest of your click handling code 10 }; 11 return ( 12 <> 13 { decision.enabled && <p>My feature is enabled</p> } 14 { !decision.enabled && <p>My feature is disabled</p> } 15 { decision.variationKey === 'control-variation' && <p>Current Variation</p> } 16 { decision.variationKey === 'experimental-variation' && <p>Better Variation</p> } 17 <button onClick={onClick}>Sign Up!</button> 18 </> 19 ); 20}
Use the built-in useTrackEvent
hook to access the track
method of optimizely instance
1import { useTrackEvent } from '@optimizely/react-sdk'; 2 3function SignupButton() { 4 const [track, clientReady, didTimeout] = useTrackEvent() 5 6 const handleClick = () => { 7 if(clientReady) { 8 track('signup-clicked') 9 } 10 } 11 12 return ( 13 <button onClick={handleClick}>Signup</button> 14 ) 15}
Or you can use the withOptimizely
HoC.
1import { withOptimizely } from '@optimizely/react-sdk'; 2 3class SignupButton extends React.Component { 4 onClick = () => { 5 const { optimizely } = this.props; 6 optimizely.track('signup-clicked'); 7 // rest of click handler 8 }; 9 10 render() { 11 <button onClick={this.onClick}>Signup</button>; 12 } 13} 14 15const 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
.
ReactSDKClient
The following type definitions are used in the ReactSDKClient
interface:
UserAttributes : { [name: string]: any }
User : { id: string | null, attributes: userAttributes }
VariableValuesObject : { [key: string]: any }
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 Feature Experimentation 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 an onReadyResult
object representing the initialization process. The instance is ready when it has fetched a datafile and a user is available (via setUser
being called with an object, or a Promise passed to setUser
becoming fulfilled). If the timeout
period happens before the client instance is ready, the onReadyResult
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.decide(key: string, options?: optimizely.OptimizelyDecideOption[], overrideUserId?: string, overrideAttributes?: optimizely.UserAttributes): OptimizelyDecision
Returns a decision result for a flag key for a user. The decision result is returned in an OptimizelyDecision object, and contains all data required to deliver the flag rule.decideAll(options?: optimizely.OptimizelyDecideOption[], overrideUserId?: string, overrideAttributes?: optimizely.UserAttributes): { [key: string]: OptimizelyDecision }
Returns decisions for all active (unarchived) flags for a user.decideForKeys(keys: string[], options?: optimizely.OptimizelyDecideOption[], overrideUserId?: string, overrideAttributes?: optimizely.UserAttributes): { [key: string]: OptimizelyDecision }
Returns an object of decision results mapped by flag keys.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 getAllFeatureVariables
is added in JavaScript SDK which is similarly returning all the feature variables, but it sends only single notification of type all-feature-variables
instead of sending for each variable. As getFeatureVariables
was added when this functionality wasn't provided by JavaScript 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: calling setForcedVariation
on a given client will trigger a re-render of all useExperiment
hooks and OptimizelyExperiment
components that are using that client.getForcedVariation(experiment: string, overrideUserId?: string): string | null
Get the forced variation for the given experiment, variation, and userTo 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 use sdkKey
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.
<OptimizelyProvider>
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.
1<OptimizelyProvider optimizely={optimizely} user={{ id: 'user1' }} isServerSide={true}> 2 <App /> 3</OptimizelyProvider>
All other Optimizely components, such as <OptimizelyFeature>
and <OptimizelyExperiment>
can remain the same.
1import * as React from 'react'; 2import * as ReactDOMServer from 'react-dom/server'; 3 4import { 5 createInstance, 6 OptimizelyProvider, 7 useDecision, 8} from '@optimizely/react-sdk'; 9 10const fetch = require('node-fetch'); 11 12function MyComponent() { 13 const [decision] = useDecision('flag1'); 14 return ( 15 <React.Fragment> 16 { decision.enabled && <p>The feature is enabled</p> } 17 { !decision.enabled && <p>The feature is not enabled</p> } 18 { decision.variationKey === 'variation1' && <p>Variation 1</p> } 19 { decision.variationKey === 'variation2' && <p>Variation 2</p> } 20 </React.Fragment> 21 ); 22} 23 24async function main() { 25 const resp = await fetch('https://cdn.optimizely.com/datafiles/<Your-SDK-Key>.json'); 26 const datafile = await resp.json(); 27 const optimizelyClient = createInstance({ 28 datafile, 29 }); 30 31 const output = ReactDOMServer.renderToString( 32 <OptimizelyProvider optimizely={optimizelyClient} user={{ id: 'user1' }} isServerSide={true}> 33 <MyComponent /> 34 </OptimizelyProvider> 35 ); 36 console.log('output', output); 37} 38main();
To disable sending all events to Optimizely's results backend, use the logOnlyEventDispatcher
when creating a client:
1import { createInstance, logOnlyEventDispatcher } from '@optimizely/react-sdk';
2
3const optimizely = createInstance({
4 datafile: window.optimizelyDatafile,
5 eventDispatcher: logOnlyEventDispatcher,
6});
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 zertosh@gmail.com 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
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 piotrek.witek@gmail.com 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:
1npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)'
Please see CONTRIBUTING for more information.
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.
Flutter - https://github.com/optimizely/optimizely-flutter-sdk
JavaScript - https://github.com/optimizely/javascript-sdk
No vulnerabilities found.
No security vulnerabilities found.