Git Product home page Git Product logo

optimizely / react-sdk Goto Github PK

View Code? Open in Web Editor NEW
89.0 63.0 35.0 1.24 MB

React SDK for Optimizely Feature Experimentation and Optimizely Full Stack (legacy)

Home Page: https://docs.developers.optimizely.com/experimentation/v4.0.0-full-stack/docs/javascript-react-sdk

License: Apache License 2.0

JavaScript 2.46% TypeScript 97.51% Shell 0.02%
optimizely-environment-prod optimizely-environment-public optimizely-owner-px

react-sdk's Introduction

Optimizely React SDK

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.

Get Started

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 for installation and implementation detail.

Features

  • 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 and hooks to use with feature flags

Compatibility

The React SDK is compatible with React 16.8.0 +

Example

import {
  createInstance,
  OptimizelyProvider,
  useDecision,
} from '@optimizely/react-sdk';

const optimizelyClient = createInstance({
  sdkKey: 'your-optimizely-sdk-key',
});

function MyComponent() {
  const [decision] = useDecision('sort-algorithm');
  return (
    <React.Fragment>
      <SearchComponent algorithm={decision.variables.algorithm} />
      { decision.variationKey === 'relevant_first' && <RelevantFirstList /> }
      { decision.variationKey === 'recent_first' && <RecentFirstList /> }
    </React.Fragment>
  );
}

class App extends React.Component {
  render() {
    return (
      <OptimizelyProvider
        optimizely={optimizelyClient}
        timeout={500}
        user={{ id: window.userId, attributes: { plan_type: 'bronze' } }}
      >
        <MyComponent />
      </OptimizelyProvider>
    );
  }
}

Install the SDK

npm install @optimizely/react-sdk

Use the React SDK

Initialization

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

  • A ReactSDKClient instance.
import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk';

const optimizely = createInstance({
  datafile: window.optimizelyDatafile,
});

<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 object
  • timeout : 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 rendering
  • userId : 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.

Readiness

Before rendering real content, both the datafile and the user must be available to the SDK.

Load the datafile synchronously

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.

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

const optimizelyClient = createInstance({
  datafile: window.optimizelyDatafile,
  sdkKey: 'your-optimizely-sdk-key', // Optimizely environment key
});

class AppWrapper extends React.Component {
  render() {
    return (
      <OptimizelyProvider optimizely={optimizelyClient} user={{ id: window.userId }}>
        <App />
      </OptimizelyProvider>
    );
  }
}

Load the datafile asynchronously

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.

import { OptimizelyProvider, createInstance, useDecision } from '@optimizely/react-sdk';

const optimizelyClient = createInstance({
  sdkKey: 'your-optimizely-sdk-key', // Optimizely environment key
});

function MyComponent() {
  const [decision, isClientReady, didTimeout] = useDecision('the-flag');
  return (
    <React.Fragment>
      { isClientReady && <div>The Component</div> }
      { didTimeout && <div>Default Component</div>}
      { /* If client is not ready and time out has not occured yet, do not render anything */ }
    </React.Fragment>
  );
}

class App extends React.Component {
  render() {
    return (
      <OptimizelyProvider
        optimizely={optimizelyClient}
        timeout={500}
        user={{ id: window.userId, attributes: { plan_type: 'bronze' } }}
      >
        <MyComponent />
      </OptimizelyProvider>
    );
  }
}

Set user asynchronously

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.optimizelyDatafile,
});

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>
    );
  }
}

useDecision Hook

A 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.

Render something if flag is enabled

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

function LoginComponent() {
  const [decision, clientReady] = useDecision(
    'login-flag',
    { autoUpdate: true },
    {
      /* (Optional) User overrides */
    }
  );
  useEffect(() => {
    document.title = decision.enabled ? 'login-new' : 'login-default';
  }, [decision.enabled]);

  return (
    <p>
      <a href={decision.enabled ? '/login-new' : '/login-default'}>Click to login</a>
    </p>
  );
}

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

  • A wrapped component with additional props as described above

Example

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

class MyComp extends React.Component {
  constructor(props) {
    super(props);
    const { optimizely } = this.props;
    const decision = optimizely.decide('feat1');    

    this.state = {
      decision.enabled,
      decision.variables,
    };
  }

  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.

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

  • Wrapped object:
    • 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

Example

import React, { useContext } from 'react';
import { OptimizelyContext } from '@optimizely/react-sdk';

function MyComponent() {
  const { optimizely, isServerSide, timeout } = useContext(OptimizelyContext);
  const decision = optimizely.decide('my-feature');
  const onClick = () => {
    optimizely.track('signup-clicked');
    // rest of your click handling code
  };
  return (
    <>
      { decision.enabled && <p>My feature is enabled</p> }
      { !decision.enabled && <p>My feature is disabled</p> }
      { decision.variationKey === 'control-variation' && <p>Current Variation</p> }
      { decision.variationKey === 'experimental-variation' && <p>Better Variation</p> }
      <button onClick={onClick}>Sign Up!</button>
    </>
  );
}

Tracking

Use the built-in useTrackEvent hook to access the track method of optimizely instance

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

function SignupButton() {
  const [track, clientReady, didTimeout] = useTrackEvent()

  const handleClick = () => {
    if(clientReady) {
      track('signup-clicked')
    }
  }

  return (
    <button onClick={handleClick}>Signup</button>
  )
}

Or you can use the withOptimizely HoC.

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.

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 instance
  • setUser(userInfo: User | Promise<User>): void Call this to update the current user
  • onUserUpdate(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
    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 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 release
  • getFeatureVariableString(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: optimizely.UserAttributes): string | null: Decide and return the variable value for the given feature, variable, and user
  • getFeatureVariableInteger(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): number | null Decide and return the variable value for the given feature, variable, and user
  • getFeatureVariableBoolean(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): boolean | null Decide and return the variable value for the given feature, variable, and user
  • getFeatureVariableDouble(featureKey: string, variableKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): number | null Decide and return the variable value for the given feature, variable, and user
  • isFeatureEnabled(featureKey: string, overrideUserId?: string, overrideAttributes?: UserAttributes): boolean Return the enabled status for the given feature and user
  • getEnabledFeatures(overrideUserId?: string, overrideAttributes?: UserAttributes): Array<string>: Return the keys of all features enabled for the given user
  • track(eventKey: string, overrideUserId?: string | EventTags, overrideAttributes?: UserAttributes, eventTags?: EventTags): void Track an event to the Optimizely results backend
  • setForcedVariation(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 user

Rollout or experiment a feature user-by-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.

Server Side Rendering

Right now server side rendering is possible with a few caveats.

Caveats

  1. You must download the datafile manually and pass in via the datafile option. Can not use sdkKey to automatically download.

  2. 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.

Setting up <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.

<OptimizelyProvider optimizely={optimizely} user={{ id: 'user1' }} isServerSide={true}>
  <App />
</OptimizelyProvider>

All other Optimizely components, such as <OptimizelyFeature> and <OptimizelyExperiment> can remain the same.

Full example

import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';

import {
  createInstance,
  OptimizelyProvider,
  useDecision,
} from '@optimizely/react-sdk';

const fetch = require('node-fetch');

function MyComponent() {
  const [decision] = useDecision('flag1');
  return (
    <React.Fragment>
      { decision.enabled && <p>The feature is enabled</p> }
      { !decision.enabled && <p>The feature is not enabled</p> }
      { decision.variationKey === 'variation1' && <p>Variation 1</p> }
      { decision.variationKey === 'variation2' && <p>Variation 2</p> }
    </React.Fragment>
  );
}

async function main() {
  const resp = await fetch('https://cdn.optimizely.com/datafiles/<Your-SDK-Key>.json');
  const datafile = await resp.json();
  const optimizelyClient = createInstance({
    datafile,
  });

  const output = ReactDOMServer.renderToString(
    <OptimizelyProvider optimizely={optimizelyClient} user={{ id: 'user1' }} isServerSide={true}>
      <MyComponent />
    </OptimizelyProvider>
  );
  console.log('output', output);
}
main();

Disabled event dispatcher

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.optimizelyDatafile,
  eventDispatcher: logOnlyEventDispatcher,
});

Additional code

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)'

Contributing

Please see CONTRIBUTING for more information.

Credits

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.

Other Optimizely SDKs

react-sdk's People

Contributors

benlorantfy avatar cristianparcu-epi avatar danny-driscoll avatar farhan-opti avatar fayyazarshad avatar frehner avatar jaeopt avatar juancarlostong avatar junaed-optimizely avatar mikechu-optimizely avatar mjc1283 avatar mnoman09 avatar msohailhussain avatar opti-jnguyen avatar rafinutshaw-optimizely avatar raju-opti avatar shaharyar123 avatar yavorona avatar zashraf1985 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-sdk's Issues

Feature Request: `useDecisions` hook

The current useDecision hook can only work with one feature flag and return only one OptimizelyDecision.

In situation where more than one decision is needed, a useDecisions hook can be used rather than using multiple useDecision hooks. This is especially important for cases where the flags used would change dynamically, for example a switchByFeatureFlags function, which accept multiple flags as argument.

TS: Property 'createUserContext' is missing in type 'OptimizelyReactSDKClient' but required in type 'ReactSDKClient' (v2.4.2)

Getting a TS error when adding OptimizelyProvider as per guide:

TypeScript error in /src/index.tsx(32,17):
Property 'createUserContext' is missing in type 'OptimizelyReactSDKClient' but required in type 'ReactSDKClient'. TS2741

    30 |             <ThemeProvider theme={theme}>
    31 |               <OptimizelyProvider
  > 32 |                 optimizely={optimizely}
       |                 ^
    33 |                 user={{
    34 |                   id: 'user123',
    35 |                 }}

React App code:

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

const optimizely = createInstance({
  sdkKey: 'XXXXXXX',
});

ReactDOM.render(
  <React.StrictMode>
    <OptimizelyProvider
        optimizely={optimizely}
        user={{ id: 'user123' }}
      >
        <App />
      </OptimizelyProvider>
  </React.StrictMode>,
  document.getElementById('root'),
);

React SDK does not work. Datafile returns empty.

image

I can view the datafile from the browser but when I try and use it in the SDK, I receive the error above. I've tried initiating it with both the sdkKey and datafile but neither works.

My code is simple. Here's the OptimizelyProvider:

<OptimizelyProvider
  optimizely={optimizely}
  user={{ id: 'user123' }}
>

And here's optimizely:

const optimizely = createInstance({
  sdkKey: 'secret',
});

Two different versions of @optimizely/js-sdk-utils in final bundle

Hello!

I'm currently working improving load times of a web-app & noticed thanks to inspectpack that on the app final bundle there are two different versions of @optimizely/js-sdk-utils, 0.1.0 & 0.4.0

see the report here:

image

I could resolve it setting a resolution to always use 0.4.0, but not sure if safe? could lead to bad functioning of the library? Also would like for this library to not introduce duplicates (& increase bundlesize/loadtimes) since I like it & use it on various projects :)

What's a feasible solution? is there any reason in particular @optimizely/react-sdk is still using @optimizely/[email protected] ?

ReactSDKClient onDataFileUpdate function

Hi,

I see in the ReactSDKClient there are functions like onUserUpdate that provide a way to get a callback when the user update happens or onReady for when the client initialises. I am wondering if there is a way to add an onDataFileUpdate function that would provide the same functionality but for when an update to the datafile occurs.

For example if I initialise the Optimizely client with:

datafileOptions: {
  autoUpdate: true,
  updateInterval: 1000
}

Then it will poll for datafile updates and if a feature flag has its value changed then it will redownload the new datafile. At this point if any of the <OptimizelyFeature> components have autoUpdate set to true then they will get updated when this new datafile with new flag value is downloaded.

If an onDataFileUpdate function with a callback was provided then we would be able to listen for these same changes using the ReactSDKClient, which allows us to do more with the feature flags, such as push the changes into Redux.

How do I mock out provider configuration?

Is there a way to pass in an object to the provider that determines which flags are on and which are off for testing purposes? I'd like to be able to run tests where I can control different combinations of flags on an off for different tests.

type VariableValuesObject doesn't handle JSON

When a variable is defined as JSON in the optimizely web interface, there is no associated typing for it on VariableValuesObject. However, the SDK handles JSON.parse-ing variables of this type via getFeatureVariableJSON. This leads to a type mismtach when using useFeature or <OptimizelyFeature />.

I might just be missing a key detail here, let me know if I'm using the SDK in an invalid way.

30KB Gzipped

If I set up a client and provider, it adds 30KB (zipped) to to the main bundle that that is almost the same size as react-dom which feels a bit excessive for something that just needs to pump in client data to a useDecision hook.

Is there anyway to reduce this overhead?

Circular structure of SDK client breaks next.js SRR

We get a SDK client on the server by running createInstance({...})

Next.js serialize all objects to json when sending them to the client.
We then get

Error: Circular structure in "getInitialProps" result of page "/_error". https://err.sh/zeit/next.js/circular-structure

The reason is that the SDKClient has a circular structure whitch is not supported in JSON

TypeError: Converting circular structure to JSON

Creating on client at the server side and the create it again in the client browser leads to a flickering behavior, since we recreate the SDK client and the turned on feature temporary goes away until we load data in the newly created client

It would be great if you could you change your SDK client to not use circular structure, so it can be serialized.

Our created SDK client looks like this

OptimizelyReactSDKClient {
  user: { id: null, attributes: {} },
  isUserPromiseResolved: false,
  onUserUpdateHandlers: [],
  initialConfig: { sdkKey: 'DNNv456Qy6tj6TuosxyyvE' },
  userPromiseResovler: [Function],
  _client:
   Optimizely {
     clientEngine: 'react-sdk',
     clientVersion: '1.0.1',
     errorHandler: NoopErrorHandler {},
     eventDispatcher: { dispatchEvent: [Function: dispatchEvent] },
     __isOptimizelyConfigValid: true,
     logger: OptimizelyLogger { messagePrefix: '' },
     projectConfigManager:
      ProjectConfigManager {
        __updateListeners: [Array],
        jsonSchemaValidator: [Object],
        skipJSONValidation: false,
        __configObj: null,
        datafileManager: [NodeDatafileManager],
        __readyPromise: [Promise] },
     __disposeOnUpdate: [Function: bound ],
     __readyPromise: Promise { <pending> },
     decisionService:
      DecisionService {
        audienceEvaluator: [AudienceEvaluator],
        forcedVariationMap: {},
        logger: [OptimizelyLogger],
        userProfileService: null },
     notificationCenter:
      NotificationCenter {
        logger: [OptimizelyLogger],
        errorHandler: NoopErrorHandler {},
        __notificationListeners: [Object],
        __listenerId: 1 },
     eventProcessor:
      LogTierV1EventProcessor {
        dispatcher: [Object],
        queue: [DefaultEventQueue],
        notificationCenter: [NotificationCenter] },
     __readyTimeouts: { '0': [Object] },
     __nextReadyTimeoutId: 1 },
  userPromise: Promise { <pending> },
  dataReadyPromise: Promise { <pending> } }

Experiment is not being evaluated sometimes - possible race condition?

Background

We are using optimizely full stack and running an experiment with 3 variations. For our frontend, we use react and hence using the optimizely react SDK.

Details of your request/issue

Expected Experience (please describe what you want):

When the user is in 'variation_3' of the experiment and visits the search results react app, they should always see the search result.

Actual Experience (please describe what you see):

When the user is in 'variation_3' of the experiment and visits the search results page, they sometimes see the search results and sometimes see a blank page.

What we suspect is the OptimizelyExperiment react component sometime does not initiate the activate and the user does not fall in the expected variation.

Is the issue in Production or a Staging/QA Environment (if in pre-production, can we access it)?

Production

Optimizely react version

@optimizely/react-sdk version 2.4.1

Browser logs

Successful experiment evaluation

[OPTIMIZELY] - INFO  2021-09-29T23:31:41.413Z DatafileManager: Updating datafile from response
[OPTIMIZELY] - INFO  2021-09-29T23:31:41.416Z PROJECT_CONFIG: Skipping JSON schema validation.
[OPTIMIZELY] - INFO  2021-09-29T23:31:41.441Z OPTIMIZELY: Updated Optimizely config to revision 1899 (project id yyyyyy)
[OPTIMIZELY] - INFO  2021-09-29T23:31:41.441Z ReactSDK: OPTIMIZELY_CONFIG_UPDATE, re-evaluating Experiment="xxxx" for user="user3"
[OPTIMIZELY] - INFO  2021-09-29T23:31:41.442Z DECISION_SERVICE: User user3 is forced in variation variation_3.
[OPTIMIZELY] - INFO  2021-09-29T23:31:41.445Z ReactSDK: Client became ready

Failure experiment evaluation

[OPTIMIZELY] - INFO  2021-09-29T23:36:51.359Z DatafileManager: Updating datafile from response
[OPTIMIZELY] - INFO  2021-09-29T23:36:51.360Z PROJECT_CONFIG: Skipping JSON schema validation.
[OPTIMIZELY] - INFO  2021-09-29T23:36:51.365Z OPTIMIZELY: Updated Optimizely config to revision 1899 (project id yyyyyy)
[OPTIMIZELY] - INFO  2021-09-29T23:36:51.369Z ReactSDK: Client became ready

In case of failure condition, we can see that the DECISION_SERVICE is not being invoked.

Any help is appreciated

Feature Request: unable to determine if a feature is turned off

Currently, when using both the useFeature hook and the OptimizelyFeature component, the only feature specific data you are returned are whether your experiment isEnabled and the variables associated with that feature. This works well when you want to toggle between two options while your feature is toggled on, but does not provide the client with any information about whether your feature is on or off. If the client receives isEnabled of false, it could mean that your feature is turned off, or it could mean that the user did not fall in the segment or group that should have the feature enabled. I'd like to propose that the hook and component return an additional value of whether the feature is toggled on or off. In our case, we would like to fallback to different functionality when the feature is off.

I'd be happy to add a PR, if this is something that your devs are willing to add.

createInstance fns.assignIn is not a function

Hello,

I am trying to setup optimizely for react application. I am using react 16.9.0 and @optimizely/react-sdk 1.0.0.

I wrapped whole application with OptimizelyProvided as stated in documentation:

const optimizely = createInstance({
  sdkKey: "MY-SDK-KEY-I-CHECKED-ITS-CORRECT",
});

class App extends React.Component {
  render() {
    return (
      <OptimizelyProvider
        optimizely={optimizely}
        user={{
          id: "test-123",
          attributes: {
            "customerId": 123,
            "Location": "poland",
          },
        }}>
      ...

But during initialization I get an error in this part of your source code:

It start here:

this._client = optimizely.createInstance(configWithClientEngine);

Then inside that function I found out that fns.assignIn is not a function, it is a module.

config = fns.assignIn(
        {
          clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE,
          eventBatchSize: DEFAULT_EVENT_BATCH_SIZE,
          eventFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL,
        },
        config,
        {
          eventDispatcher: eventDispatcher,
          // always get the OptimizelyLogger facade from logging
          logger: logger,
          errorHandler: logging.getErrorHandler(),
        }
      );

which leads us to situation where

this._client

is null, and in below line the onReady function on null cannot be triggered

this.dataReadyPromise = Promise.all([this.userPromise, this._client.onReady()])

May this issue be connected with webpack configuration? Could you help me with that problem?

Allow `default` to be set on an OptimizelyVariation that has `variation` also set

This would be a nice quality of life improvement to have as we often have experiment code that looks like this:

<OptimizelyExperiment experiment="some_test">
  <OptimizelyVariation variation="original">
    <OriginalComponent />
  </OptimizelyVariation>
  <OptimizelyVariation variation="some_variant">
    <VariantComponent />
  </OptimizelyVariation>
  <OptimizelyVariation default>
    <OriginalComponent />
  </OptimizelyVariation>
</OptimizelyExperiment>

It would be nice to be able to express the above as:

<OptimizelyExperiment experiment="some_test">
  <OptimizelyVariation default variation="original">
    <OriginalComponent />
  </OptimizelyVariation>
  <OptimizelyVariation variation="some_variant">
    <VariantComponent />
  </OptimizelyVariation>
</OptimizelyExperiment>

As it is more concise and just as readable.

Is there a chance of seeing this improvement land in the SDK?

-Dave

Merge Attributes on Override Attributes and/or Pass User Attributes as Context

We have a base set of attributes that are passed to our OptimizelyProvider upon initialization. A common use case is in a hook to pass the overrideAttributes. Generally we would like to have our base attributes merged with the overrideAttributes. It looks like the OptimizelyProvider uses the .setUser method and then in the hook calls .activate with the third argument of the override attributes. I'm under the impression that this will completely override our base set of attributes.

Currently, if we pass overrides we have to access our base attributes from state or context and manually merge them in the hook. I would be nice if they were passed as context and either the hook merges them internally or we can use the Optimizely context to access the base attributes and perform the merge with overrides.

withOptimizely HOC and optimizely onReady state.

Hi, is it correct behaviour that when we use
withOptimizely HOC we always should check props.optimizely for ready state before use ?
I mean

const Menu = props => {
  useEffect(() => {
    // we use 
    props.optimizely.onReady().then(() => {
     console.log(props.optimizely.isFeatureEnabled('feature'));
    });
    
    // instead of using 
    console.log(props.optimizely.isFeatureEnabled('feature'));
  }, []);

  return </>;
};

export default withOptimizely(Menu);

I'm asking because in documentation there is an example without onReady check.
And If we do like in example we've got next error:
OPTIMIZELY: Optimizely object is not valid. Failing isFeatureEnabled.

Also my concern is that when we using OptimizelyFeature component we don't need to do additional checking but for withOptimizely we should. .

CONFIG_VALIDATOR: No datafile specified. Cannot start optimizely.

Upon upgrading to 2.3.X, we get the the following error CONFIG_VALIDATOR: No datafile specified. Cannot start optimizely., however Optimizely seems to load everything after the datafile is updated from the http response for the datafile.

Screen Shot 2020-10-06 at 9 06 05 AM

Is there a way to turn off the initial error message? So far I've tried increasing the timeout in the OptimizelyProvider to no avail.

@Optimizely causing TS errors

My Setup/Config
Running: "@optimizely/react-sdk": "^2.7.0",
"typescript": "^4.4.4"

Tsconfig.ts
has the following in excludes: "node_modules/**/*"
has "skipLibCheck": true set in compilerOptions

Hello, when running the command below @optimizely has started to create 12 errors
tsc --noEmit --project tsconfig.json

Two sample errors:

node_modules/@optimizely/optimizely-sdk/lib/core/project_config/index.ts:167:19 - error TS2532: Object is possibly 'undefined'.

167     experiments = projectConfig.groupIdMap[Id].experiments;
                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@optimizely/optimizely-sdk/lib/core/project_config/index.ts:222:11 - error TS2532: Object is possibly 'undefined'.

222           projectConfig.experimentFeatureMap[experimentId].push(feature.id);

Any idea why this might be happening all of a sudden? Thank you!

(Side note: seems similar to this older issue optimizely/javascript-sdk#613)

What is the difference between useFeature and useDecision hooks?

Hello 👋

I see that since 2.5 version you introduced useDecision hook and updated all docs to use it, but I am not able to find any motivation behind that. Also now there are no docs about useFeature hook, that leads to confusion over should we continue use it or not.

Can you please explain the differences of useFeature and useDecision hooks and what are the future support plans for useFeature hook since useDecision was introduced?

Thanks

Mocking `isFeatureEnabled` in unit tests

Problem

I'm running unit tests on some components that use feature flags. I want to set all features to enabled every time. I've tried mocking the isFeatureEnabled function but have had no luck so far.

It'd be great if there was a way to configure all flags to be enabled in the createInstance config.

Example

Here's a simplified example of my tests.

import { render, screen } from '@testing-library/react';

export const mockOptimizely = createInstance({});

const MyComponent = () => {
  const [isEnabled] = useFeature('my-feature');

  console.log('isEnabled: ', isEnabled);

  return <div>{isEnabled ? 'Is enabled' : 'Not enabled'}</div>;
};

describe('OptimizelyDependant', () => {
  beforeEach(() => {
    mockOptimizely.isFeatureEnabled = jest.fn().mockReturnValue(true);
  });

  it('passes', () => {
    render(
      <OptimizelyProvider optimizely={mockOptimizely} user={{ id: '123' }}>
        <MyComponent />
      </OptimizelyProvider>
    );
    expect(screen.queryByText('Is enabled')).toBeTruthy();
  });
});

Versions

Package Version
react 17.0.2
@optimizely/react-sdk 2.7.0
jest 26.6.0
@testing-library/react 12.0.0

SDK-utils not updated

Getting the error TypeError: Cannot read property 'OPTIMIZELY_CONFIG_UPDATE' of undefined
After installing "@optimizely/js-sdk-utils": "^0.2.0", it works, by default it install v0.1.0

new console warning in 2.2.0

I'm getting a new warning in 2.2.0 that doesn't appear if I downgrade to 2.1.0. It's not quite clear what the issue is based on the warning so I'm not sure how to fix it:

[OPTIMIZELY] - WARN 2020-08-05T16:28:18.546Z BUCKETER: Bucketed into an invalid variation ID. Returning null.

My implementation looks like the following:

const optimizely = createInstance({
  sdkKey: key,
  logLevel: 3,
})

function App() {
  const user = useUser()
  return <OptimizelyProvider
      optimizely={optimizely}
      timeout={500}
      user={{ id: user ? user.email : '' }} //have to wait for the user api to resolve
  >
    // rest of app as children
  </OptimizelyProvider>
}

also, we're only using the feature toggles at the moment in our app, nothing else.

Can you guide me as to what the warning is and how to correct it? Thanks.

User cannot be unset

Once the user has been set, it cannot be unset anymore (e.g. for the case the user logged out or withdrew GDPR consent).

Neither user={undefined} nor user={{id: null}} work because of this code:

Screenshot 2021-10-27 at 16 47 01

Screenshot 2021-10-27 at 16 52 06

createInstance can throw: how to graceful fallback

There are a few issues that make it impossible for the application to still work if optimizely fails to initialize:

  1. optimizely.createInstance from optimizely-sdk can return null
    https://github.com/optimizely/javascript-sdk/blob/v4.7.0/packages/optimizely-sdk/lib/index.browser.ts#L136
  2. createInstance in the react-sdk does not handle the null case and then just throws in the constructor when accessing this._client.onReady
    https://github.com/optimizely/react-sdk/blob/2.7.0/src/client.ts#L195-L201
  3. if we catch this thrown error in our application code, we cannot render OptimizelyProvider because it needs an OptimizelyReactSDKClient instance
    https://github.com/optimizely/react-sdk/blob/2.7.0/src/Provider.tsx#L27
  4. conditionally rendering the OptimizelyProvider is not an option because the optimizely hooks from react-sdk throw when there is no optimizely instance provided via the OptimizelyProvider
    https://github.com/optimizely/react-sdk/blob/2.7.0/src/hooks.ts#L332-L336
  5. the rules of hooks disallow to render hooks conditionally, so we cannot catch this issue on the application side

We currently see in production ~70.000 failed optimizely initializations / hour. All happening in Firefox, and we need to investigate more, but the main issue is that failing optimizely makes the application unusable, which is not a graceful fallback.

I have different ideas to solve this:

  1. react-sdk allows to render OptimizelyProvider with optimizely={null} and the optimizely hooks are modified to not throw anymore when no optimizely instance is available. Applications will need to catch the thrown error from the initial createInstance call and as a fallback use null. The hooks then fall back to the same values they would when optimizely is not yet ready.
  2. react-sdk does not throw in createInstance when optimizely is null and rather wraps all accesses to this._client with a null check.
  3. react-sdk provides a DummyReactClient that implements all the functions from ReactSDKClient with no functionality. Applications will need to catch the thrown error from the initial createInstance call and as a fallback use the DummyReactClient and provide it to OptimizelyProvider.

I would vote for option 1. because it still allows applications to detect when createInstance fails and allows for the maximum flexibility. Also it's backwards-compatible.

React-Redux "connect" not working with "withOptimizely"

I am trying to connect Optimizely with a component connected with react-redux.

I have the following code

export class Report extends Component {
  constructor(props) {
    super(props);
    ....   
}

export default connect(mapStateToProps, { abTestCreate, reportResetErrors })(Report);

I need to connect that component using "withOptimizely", I tried the following 2 approaches and they didn't work, not sure if there is something I can do

const ConnectedReport = connect(mapStateToProps, { abTestCreate, reportResetErrors })(Report);
export default withOptimizely(ConnectedReport);
const OptimizelyReport = withOptimizely(Report);
export default connect(mapStateToProps, { abTestCreate, reportResetErrors })(OptimizelyReport);

Any suggestion about how to make them work together please ?

setForcedVariation does not work as expected

SDK version 2.5.0
React Version 16.14.0

Description

I'm creating the client by instantiating it with the datafile, and then based upon query parameters for experiment id and variation name I'm calling client.setForcedVariation in a useMemo hook

Expected

For variation supplied in the query param to be forced

Actual

No variation is forced and user will be bucket as normal

Setup

import React, { FC, useMemo } from 'react';
import { createInstance, logOnlyEventDispatcher, OptimizelyProvider, ReactSDKClient } from '@optimizely/react-sdk';
import { eventDispatcher } from './eventDispatcher';
import { isServer } from './isServer';
import { logger } from './logger';
import { Datafile, FullstackProviderAttributes } from './types';

export const OptimizelyFullstackProvider: FC<{
  datafile: Datafile;
  attributes: FullstackProviderAttributes;
}> = ({ datafile, attributes = {}, children }) => {
  const isClient = !isServer();
  const { disableTracking, id, variation, experiment, ...restAttributes } = attributes;
  const optimizelyClient = useMemo(() => {
    const client = createInstance({
      eventDispatcher: isClient && !disableTracking ? eventDispatcher : logOnlyEventDispatcher,
      datafile,
    });

    if (variation && experiment) {
      logger.info(`forcing variation ${variation} for experiment ${experiment} and user ${id}`);
      client.setForcedVariation(experiment, variation);
    }

    return client;
  }, [datafile, disableTracking, isClient, experiment, variation, id]);

  return (
    <OptimizelyProvider isServerSide optimizely={optimizelyClient} user={{ id, attributes: restAttributes }}>
      {children}
    </OptimizelyProvider>
  );
};

Feature request: useFeature should watch for forced variations on the client

Currently the useExperiments hook adds a listener to the client to fetch decisions if a variation has been forced. Since the useFeature hook also uses decisions if an experiment is active, it makes sense that it should also fetch the decisions, but this is currently not the case.

This can be tested by setting up an experiment and using the useFeature hook. You'll notice that if you force a variation, the useFeature properties isFeatureEnabled and variables are never updated.

Updating @optimizely/js-sdk-logging dependency

Currently the optimizely/js-sdk-logging dependency is "^0.1.0". Bc caret behavior for 0.x versions only match patch versions, it doesn't resolve to the most current ver: "^0.3.1". Are there any plans to update this dep?

I'm asking bc the optimizely/javascript-sdk repo has its @optimizely/js-sdk-logging dependency at "^0.3.1". So having optimaizely/react-sdk and optimizely/javascript-sdk in my project means I have both versions "0.1.0" and "0.3.1" of optimizely/js-sdk-logging in my bundle.

build to umd (and system?) format?

Wondering if you would be open to building to umd (and system) formats?

looking at https://github.com/optimizely/react-sdk/blob/master/scripts/build.js it seems like we could just add another line like the following

exec(`./node_modules/.bin/rollup -c scripts/config.js -f umd -o dist/${packageName}.umd.js`);

// and maybe system format?
exec(`./node_modules/.bin/rollup -c scripts/config.js -f system -o dist/${packageName}.system.js`);

shouldn't have any side effects, since the entry points will remain the same and these are just new files being built.

if you're open to it, I can make the PR as well

Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'

Happens while building (babel always turns on --isolatedModules flag)

Failed to compile.

./node_modules/@optimizely/optimizely-sdk/lib/core/event_processor/index.ts:25:10
Type error: Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'.

23 | }
24 |
> 25 | export { EventProcessor, LocalStoragePendingEventsDispatcher } from '@optimizely/js-sdk-event-processor';
|......................^
26 |
27 | export default { createEventProcessor, LocalStoragePendingEventsDispatcher };
28 |

Optimizely breaks Gatsby

When attempting a build in Gatsby optimizely throws several es export / import errors

FYI I used this as an example: https://github.com/circAssimilate/gatsby-with-optimizely-full-stack

$ gatsby build
success open and validate gatsby-configs - 0.009s
success load plugins - 0.110s
success onPreInit - 0.004s
success delete html and css files from previous builds - 0.008s
success initialize cache - 0.006s
success copy gatsby files - 0.049s
success onPreBootstrap - 0.018s
success createSchemaCustomization - 0.002s
success source and transform nodes - 0.038s
success building schema - 0.136s
success createPages - 0.305s
success createPagesStatefully - 0.021s
success onPreExtractQueries - 0.002s
warn There are conflicting field types in your data.

If you have explicitly defined a type for those fields, you can safely ignore this warning message.
Otherwise, Gatsby will omit those fields from the GraphQL schema.

If you know all field types in advance, the best strategy is to explicitly define them with the `createTypes` action, and skip inference with
 the `@dontInfer` directive.
SitePage.context.datafile.typedAudiences.conditions:
 - type: [string,array]
   value: [ 'and', [  ] ]
success update schema - 0.094s
success extract queries from components - 0.021s
success write out requires - 0.009s
success write out redirect data - 0.002s
success onPostBootstrap - 0.001s
⠀
info bootstrap finished - 3.066 s
⠀
success Building production JavaScript and CSS bundles - 7.723s
success Rewriting compilation hashes - 0.006s
success run queries - 7.869s - 1/1 0.13/s

 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'Children' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'Component' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'Component' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'Component' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'Component' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'Component' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'cloneElement' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'createContext' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'createElement' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'createElement' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'createElement' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'createElement' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'createInstance' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'forwardRef' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'getLogger' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'getLogger' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'getLogger' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'getLogger' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'getLogger' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't import the named export 'isValidElement' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't reexport the named export 'enums' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't reexport the named export 'errorHandler' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't reexport the named export 'eventDispatcher' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't reexport the named export 'logging' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't reexport the named export 'setLogLevel' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs


 ERROR #98123  WEBPACK

Generating SSR bundle failed

Can't reexport the named export 'setLogger' from non EcmaScript module (only default export is available)

File: node_modules/@optimizely/react-sdk/dist/react-sdk.mjs

not finished Building static HTML for pages - 1.094s
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Optizely instace with custom headers

I should be able to send a custom header (eg Authorization) within the request considering the fact that I can point my instance to another endpoint which may need other kinds of information

const optimizely = createInstance({
    sdkKey: 'development',
    headers: { Authorization: "23984712348213g48b8sgd8f" } // suggestion
    datafileOptions: {
      urlTemplate: 'http://localhost:3001/optimizely/datafile/%s',
    },
  })

Integrating with React Redux

My apologies if this has been addressed before - I have searched through the docs, issues & closed issues to no avail.

Do you have guidelines for integrating the optimizely SDK into redux? For our project we have done some hacky work to listen to the OptimizelyFeature status and synchronise the experiment status to the store when it gets mounted, however this feels more like a workaround than a feature.

Wondering if you have any best-practice guidelines for integrating with Redux? Many thanks.

Question about user id

I'm still learning how to use optimizely, so please bear with me. How are you suppose to configure an audience based on the user {id} that is passed in. I want to configure a feature flag with a specific audience that is based of a set of user id. However, when i try to configure an audience with the attribute (id) it doesn't work. The only way i can do it is if i add the id as an attribute to the attribute object.

  <OptimizelyProvider
              optimizely={this.optimizely}
              user={{
                id: userEmail,
                attributes: { id: userEmail } 
              }}

Thanks,
Derek

Does react-sdk use any DOM APIs? Is react-native supported?

Hello Optimizely!

I'm investigating options for doing experiments in my react-native app. Does this SDK use any DOM/browser APIs that might block me from using the sdk with react-native?

I verified that it works with react-native, but should I expect it will continue working that way?

Types not importable for notification listeners

Hello,

I wanted to attach a notification listener to my team's client instance so we can create an event and pass along to our analytics provider. Specifically, I wanted to attach an ACTIVATE listener. I'd like to create the callback for this outside the call to addNotificationListener and have it be typed. I see the type that this function is expecting is :

NotificationListener<T extends ListenerPayload> = (notificationData: T) => void;

which comes from @optimizely/optimizely-sdk .

The file containing this type also contains other derived types like: ActivateListenerPayload .
While I can derive the less specific type ListenerPayload like this:

type Callback = Parameters<typeof optimizely.notificationCenter.addNotificationListener>[1];

I wish I could import the type from @optimizely/react-sdk via this package , but the types are not exported here.
Furthermore this is the more generic type and what I truly want to use in this case is ActivateListenerPayload .

Is there a reason the types are not exported for use? Or advise what to do in this case to make my function type safe? Thank you!

Simple example produces console warning: Can't perform a React state update on an unmounted component

Just started integration of Optimizely's React SDK into my React project. I've used the SDK set up guide, and the product is functional: I successfully get the dev JSON datafile, it loads and checks if I have access to the feature - which I do.

However, I receive the console warning attached - pardon the length, I wanted to provide full context.

I have tried creating a timeout of 1 millisecond on the OptimizelyProvider (gives fail to initialize onReady before timeout and the same "Can't perform a React state update..." warning). I have also varied the locatino of OptimizelyProvider, starting at the highest level of the application, and brought it down several "rings" until after auth state is known (so it only triggers once when a known user is set). Additionally, I have the simplest possible set up and an OptimizelyFeature straight out of the docs.

At this point, it seems the issue is with the react-sdk, especially since the warning is most related to FeatureComponent, created by Context.Consumer.

Key versions:

  • @optimizely/react-sdk: 1.0.1
  • react: 16.11.0
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.835Z DatafileManager: Updating datafile from response
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.837Z PROJECT_CONFIG: Skipping JSON schema validation.
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.840Z OPTIMIZELY: Updated Optimizely config to revision 15 (project id 17165161924)
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.840Z <OptimizelyFeature>: feature="abc" successfully rendered for user="[email protected]"
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.842Z DECISION_SERVICE: Audiences for experiment 17283183885 collectively evaluated to TRUE.
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.843Z BUCKETER: User [email protected] is in variation 17273943456 of experiment 17283183885.
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.843Z OPTIMIZELY: Feature abc is enabled for user [email protected].
index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in FeatureComponent (created by Context.Consumer)
    in WithOptimizely (created by ForwardRef(withOptimizely(FeatureComponent)))
    in ForwardRef(withOptimizely(FeatureComponent)) (at Device.tsx:313)
    in div (at Device.tsx:194)
    in div (at Page.tsx:20)
    in div (at Page.tsx:11)
    in Page (at Device.tsx:164)
    in Device (at routes.tsx:59)
    in Route (at routes.tsx:46)
    in SecureRoute (at routes.tsx:92)
>> additional detail
console.<computed> @ index.js:1
warningWithoutStack @ react-dom.development.js:530
warnAboutUpdateOnUnmountedFiberInDEV @ react-dom.development.js:25738
scheduleUpdateOnFiber @ react-dom.development.js:23679
enqueueSetState @ react-dom.development.js:13994
push../node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:325
(anonymous) @ react-sdk.js:644
Promise.then (async)
FeatureComponent.componentDidMount @ react-sdk.js:635
commitLifeCycles @ react-dom.development.js:22101
commitLayoutEffects @ react-dom.development.js:25344
callCallback @ react-dom.development.js:336
invokeGuardedCallbackDev @ react-dom.development.js:385
invokeGuardedCallback @ react-dom.development.js:440
commitRootImpl @ react-dom.development.js:25082
unstable_runWithPriority @ scheduler.development.js:697
runWithPriority$2 @ react-dom.development.js:12149
commitRoot @ react-dom.development.js:24922
finishSyncRender @ react-dom.development.js:24329
performSyncWorkOnRoot @ react-dom.development.js:24307
(anonymous) @ react-dom.development.js:12199
unstable_runWithPriority @ scheduler.development.js:697
runWithPriority$2 @ react-dom.development.js:12149
flushSyncCallbackQueueImpl @ react-dom.development.js:12194
flushSyncCallbackQueue @ react-dom.development.js:12182
batchedUpdates$1 @ react-dom.development.js:24392
notify @ Subscription.js:23
notifyNestedSubs @ Subscription.js:65
handleChangeWrapper @ Subscription.js:70
dispatch @ redux.js:221
e @ VM245:1
(anonymous) @ middleware.js:22
dispatch @ VM245:1
(anonymous) @ redux.js:475
(anonymous) @ App.tsx:43
Promise.then (async)
getUser @ App.tsx:40
AppBase @ App.tsx:53
renderWithHooks @ react-dom.development.js:16260
mountIndeterminateComponent @ react-dom.development.js:18794
beginWork$1 @ react-dom.development.js:20162
beginWork$$1 @ react-dom.development.js:25756
performUnitOfWork @ react-dom.development.js:24698
workLoopSync @ react-dom.development.js:24671
performSyncWorkOnRoot @ react-dom.development.js:24270
scheduleUpdateOnFiber @ react-dom.development.js:23698
updateContainer @ react-dom.development.js:27103
(anonymous) @ react-dom.development.js:27528
unbatchedUpdates @ react-dom.development.js:24433
legacyRenderSubtreeIntoContainer @ react-dom.development.js:27527
render @ react-dom.development.js:27608
./src/index.tsx @ index.tsx:25
__webpack_require__ @ bootstrap:785
fn @ bootstrap:150
1 @ Utility.ts:126
__webpack_require__ @ bootstrap:785
checkDeferredModules @ bootstrap:45
webpackJsonpCallback @ bootstrap:32
(anonymous) @ main.chunk.js:1
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.845Z <OptimizelyFeature>: feature="abc" successfully rendered for user="[email protected]"
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.845Z DECISION_SERVICE: Audiences for experiment 17283183885 collectively evaluated to TRUE.
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.846Z BUCKETER: User [email protected] is in variation 17273943456 of experiment 17283183885.
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.846Z OPTIMIZELY: Feature abc is enabled for user [email protected].
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.846Z <OptimizelyFeature>: feature="abc" successfully rendered for user="[email protected]"
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.846Z DECISION_SERVICE: Audiences for experiment 17283183885 collectively evaluated to TRUE.
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.847Z BUCKETER: User [email protected] is in variation 17273943456 of experiment 17283183885.
[OPTIMIZELY] - INFO  2020-01-08T20:10:13.847Z OPTIMIZELY: Feature abc is enabled for user [email protected].

Could not import @optimizely/react-sdk

After I used
yarn add @optimizely/react-sdk
And it was beed added in package.json
image

But when I import it in React like this
image
It will report warning in console.
image

And I debug in the frontend, I will stop at the point of import
image

Did I do something wrong or need some other settings?
Thanks for helping!

Ability to turn off INFO logging?

Hi 👋 Is there a way to disable all the INFO logging messages that are output to the browser's console?

[OPTIMIZELY] - INFO  2020-09-09T18:06:27.879Z DatafileManager: Updating datafile from response
[OPTIMIZELY] - INFO  2020-09-09T18:06:27.880Z PROJECT_CONFIG: Skipping JSON schema validation.
[OPTIMIZELY] - INFO  2020-09-09T18:06:27.881Z OPTIMIZELY: Updated Optimizely config to revision 103 (project id 1234)

If not, would you all be open to a PR for this?

Issues with SSR since @optimizely/react-sdk@^2.0.0

I am trying to integrate @optimizely/react-sdk with a project that uses the next.js framework. Everything was working fine, but since the release of @optimizely/react-sdk@^2.0.0 there is a problem with the initialization of optimizely instance on the client-side.

Even though I am providing the datafile that is fetched during server-side rendering, optimizely.isReady() initially returns false.

So my experiments that are defined like below:

<p>Experiments:</p>
<OptimizelyExperiment experiment="DEV_experiment">
  {variation => <p>got variation {variation}</p>}
</OptimizelyExperiment>
<hr/>
<OptimizelyExperiment experiment="DEV_experiment">
  <OptimizelyVariation variation="variation_1">
    variation_1
  </OptimizelyVariation>
  <OptimizelyVariation variation="variation_2">
    variation_2
  </OptimizelyVariation>
  <OptimizelyVariation default>
    default
  </OptimizelyVariation>
</OptimizelyExperiment>

During the server-side render the content for variation_2.

<p>Experiments:</p><p>got variation <!-- -->variation_2</p><hr/>variation_2</div>

But in the browser, it cannot retrieve it for some reason.

<p>Experiments:</p><p>got variation </p><hr>default</div>

Instead, I can see logs in the console that Client became ready (https://github.com/optimizely/react-sdk/blob/master/src/hooks.ts#L132). However, since I provided datafile, should not the client be ready right after initialization?

To Reproduce

You can use this repository to see the issue: https://github.com/oMatej/nextjs-optimizely

I think it can be something related to: #58

breaks all existing tests

Adding optimizely rollouts to my react database breaks all my unit and integration tests. how do I mock out the optimizely wrapper in jest?

useDecision not evaluating correctly if user is updated with setUser

After initialization, if I change the user associated with the ReactSDKClient using its setUser method, the decisions produced by useDecision for flagKeys that have been used previously do not evaluate correctly (the userContext for the decision is still the old user).

I think the error is due to the following check: if (!areDecisionInputsEqual(prevDecisionInputs, currentDecisionInputs)) {

The above check doesn't take into consideration the ReactSDKClient's current user.

If I use the overrides in the useDecision call e.g.

const result = useDecision(key, undefined, {
  overrideUserId: OptimizelyClient.user.id,
  overrideAttributes: OptimizelyClient.user.attributes,
});

then everything works as expected. But I shouldn't have to use the overrides.

Thanks.

useFeature returns flag as `false` for one frame on page load, then `true`

Everything about our setup seems pretty normal. This is what our code looks like in a FC, with unrelated code stripped out.

const [foo] = useFeature('foo');
console.log(foo);

Console output:

false
true

This is resulting in us rendering a big chunk of old UI for a single frame, then the new UI.

Replace @react-native-community/async-storage with @react-native-async-storage/async-storage

In the docs is says

yarn add @react-native-community/async-storage
yarn add @react-native-community/netinfo

However @react-native-community/async-storage is a deprecated project, and has been replaced by @react-native-async-storage/async-storage (with the same API, for now). Please update to use the modern tool. Using optimizely's react sdk in React Native projects will not allow using the new tool, as it expects @react-native-community/async-storage to be present.

Perhaps the best way to build storage in is dependency injection. Storybook is initialized this way, allowing to specify your instance of async storage

import AsyncStorage form "@react-native-async-storage/async-storage";
// or import AsyncStorage from "@react-native-community/async-storage";

getStorybookUI({
  ...
  asyncStorage: AsyncStorage
});

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.