npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@anomen/react-ab-test

v1.7.0

Published

A/B testing React components and debug tools. Isomorphic with a simple, universal interface. Well documented and lightweight. Tested in popular browsers and Node.js. Includes helpers for Mixpanel and Segment.com.

Downloads

3

Readme

A/B Testing React Components

NPM Version Circle CI Coverage Status Dependency Status NPM Downloads

Wrap components in <Variant /> and nest in <Experiment />. A variant is chosen randomly and saved to local storage.

<Experiment name="My Example">
  <Variant name="A">
    <div>Version A</div>
  </Variant>
  <Variant name="B">
    <div>Version B</div>
  </Variant>
</Experiment>

Report to your analytics provider using the emitter. Helpers are available for Mixpanel and Segment.com.

emitter.addPlayListener(function(experimentName, variantName){
  mixpanel.track("Start Experiment", {name: experimentName, variant: variantName});
});

Please ★ on GitHub!

Installation

react-ab-test is compatible with React 0.14.x and 0.15.x.

npm install react-ab-test

Usage

Standalone Component

Try it on JSFiddle


var Experiment = require("react-ab-test/lib/Experiment");
var Variant = require("react-ab-test/lib/Variant");
var emitter = require("react-ab-test/lib/emitter");

var App = React.createClass({
  onButtonClick: function(e){
    this.refs.experiment.win();
  },
  render: function(){
    return <div>
      <Experiment ref="experiment" name="My Example">
        <Variant name="A">
          <div>Section A</div>
        </Variant>
        <Variant name="B">
          <div>Section B</div>
        </Variant>
      </Experiment>
      <button onClick={this.onButtonClick}>Emit a win</button>
    </div>;
  }
});

// Called when the experiment is displayed to the user.
emitter.addPlayListener(function(experimentName, variantName){
  console.log("Displaying experiment ‘" + experimentName + "’ variant ‘" + variantName + "’");
});

// Called when a 'win' is emitted, in this case by this.refs.experiment.win()
emitter.addWinListener(function(experimentName, variantName){
  console.log("Variant ‘" + variantName + "’ of experiment ‘" + experimentName + "’  was clicked");
});

Coordinate Multiple Components

Try it on JSFiddle


var Experiment = require("react-ab-test/lib/Experiment");
var Variant = require("react-ab-test/lib/Variant");
var emitter = require("react-ab-test/lib/emitter");

// Define variants in advance.
emitter.defineVariants("My Example", ["A", "B", "C"]);

var Component1 = React.createClass({
  render: function(){
    return <Experiment name="My Example">
      <Variant name="A">
        <div>Section A</div>
      </Variant>
      <Variant name="B">
        <div>Section B</div>
      </Variant>
    </Experiment>;
  }
});

var Component2 = React.createClass({
  render: function(){
    return <Experiment name="My Example">
      <Variant name="A">
        <div>Subsection A</div>
      </Variant>
      <Variant name="B">
        <div>Subsection B</div>
      </Variant>
      <Variant name="C">
        <div>Subsection C</div>
      </Variant>
    </Experiment>;
  }
});

var Component3 = React.createClass({
  onButtonClick: function(e){
    emitter.emitWin("My Example");
  },
  render: function(){
    return <button onClick={this.onButtonClick}>Emit a win</button>;
  }
});

var App = React.createClass({
  render: function(){
    return <div>
      <Component1 />
      <Component2 />
      <Component3 />
    </div>;
  }
});

// Called when the experiment is displayed to the user.
emitter.addPlayListener(function(experimentName, variantName){
  console.log("Displaying experiment ‘" + experimentName + "’ variant ‘" + variantName + "’");
});

// Called when a 'win' is emitted, in this case by emitter.emitWin()
emitter.addWinListener(function(experimentName, variantName){
  console.log("Variant ‘" + variantName + "’ of experiment ‘" + experimentName + "’ was clicked");
});

Weighting Variants

Try it on JSFiddle

Use emitter.defineVariants() to optionally define the ratios by which variants are chosen.


var Experiment = require("react-ab-test/lib/Experiment");
var Variant = require("react-ab-test/lib/Variant");
var emitter = require("react-ab-test/lib/emitter");

// Define variants and weights in advance.
emitter.defineVariants("My Example", ["A", "B", "C"], [10, 40, 40]);

var App = React.createClass({
  render: function(){
    return <div>
      <Experiment ref="experiment" name="My Example">
        <Variant name="A">
          <div>Section A</div>
        </Variant>
        <Variant name="B">
          <div>Section B</div>
        </Variant>
        <Variant name="C">
          <div>Section C</div>
        </Variant>
      </Experiment>
    </div>;
  }
});

Debugging

The debugger attaches a fixed-position panel to the bottom of the <body> element that displays mounted experiments and enables the user to change active variants in real-time.

The debugger is wrapped in a conditional if(process.env.NODE_ENV === "production") {...} and will not display on production builds using envify.

Try it on JSFiddle


var Experiment = require("react-ab-test/lib/Experiment");
var Variant = require("react-ab-test/lib/Variant");
var experimentDebugger = require("react-ab-test/lib/debugger");

experimentDebugger.enable();

var App = React.createClass({
  render: function(){
    return <div>
      <Experiment ref="experiment" name="My Example">
        <Variant name="A">
          <div>Section A</div>
        </Variant>
        <Variant name="B">
          <div>Section B</div>
        </Variant>
      </Experiment>
    </div>;
  }
});

Server Rendering

A <Experiment /> with a userIdentifier property will choose a consistent <Variant /> suitable for server side rendering.

See ./examples/isomorphic for a working example.

Example

The component in Component.jsx:


var React = require("react");
var Experiment = require("react-ab-test/lib/Experiment");
var Variant = require("react-ab-test/lib/Variant");

module.exports = React.createClass({
  propTypes: {
    userIdentifier: React.PropTypes.string.isRequired
  },
  render: function(){
    return <div>
      <Experiment ref="experiment" name="My Example" userIdentifier={this.props.userIdentifier}>
        <Variant name="A">
          <div>Section A</div>
        </Variant>
        <Variant name="B">
          <div>Section B</div>
        </Variant>
      </Experiment>
    </div>;
  }
});

We use a session ID for the userIdentifier property in this example, although a long-lived user ID would be preferable. See server.js:

require("babel/register")({only: /jsx/});

var express = require('express');
var session = require('express-session');
var React = require("react");
var ReactDOMServer = require("react-dom/server");
var Component = require("./Component.jsx");

var app = express();

app.set('view engine', 'ejs');

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}));

app.get('/', function (req, res) {
  var reactElement = React.createElement(Component, {userIdentifier: req.sessionID});
  var reactString = ReactDOMServer.renderToString(reactElement);
  res.render('template', {
    sessionID: req.sessionID,
    reactOutput: reactString
  });
});

app.use(express.static('www'));

app.listen(8080);

An EJS template in template.ejs:

<!doctype html>
<html>
  <head>
    <title>Isomorphic Rendering Example</title>
  </head>
  <script type="text/javascript">
    var SESSION_ID = <%- JSON.stringify(sessionID) %>;
  </script>
  <body>
    <div id="react-mount"><%- reactOutput %></div>
    <script type="text/javascript" src="bundle.js"></script>
  </body>
</html>

On the client in app.jsx:

var React = require('react');
var ReactDOM = require('react-dom');
var Component = require("../Component.jsx");

var container = document.getElementById("react-mount");

ReactDOM.render(<Component userIdentifier={SESSION_ID} />, container);

With Babel

Code from ./src is written in JSX and transpiled into ./lib using Babel. If your project uses Babel you may want to include files from ./src directly.

Alternative Libraries

Please let us know about alternate libraries not included here.

Resources for A/B Testing with React

Please let us know about React A/B testing resources not included here.

API Reference

<Experiment />

Experiment container component. Children must be of type Variant.

  • Properties:
    • name - Name of the experiment.
      • Required
      • Type: string
      • Example: "My Example"
    • userIdentifier - Distinct user identifier. When defined, this value is hashed to choose a variant if defaultVariantName or a stored value is not present. Useful for server side rendering.
      • Optional
      • Type: string
      • Example: "7cf61a4521f24507936a8977e1eee2d4"
    • defaultVariantName - Name of the default variant. When defined, this value is used to choose a variant if a stored value is not present. This property may be useful for server side rendering but is otherwise not recommended.
      • Optional
      • Type: string
      • Example: "A"

<Variant />

Variant container component.

  • Properties:
    • name - Name of the variant.
      • Required
      • Type: string
      • Example: "A"

emitter

Event emitter responsible for coordinating and reporting usage. Extended from facebook/emitter.

emitter.emitWin(experimentName)

Emit a win event.

  • Return Type: No return value
  • Parameters:
    • experimentName - Name of an experiment.
      • Required
      • Type: string
      • Example: "My Example"

emitter.addActiveVariantListener([experimentName, ] callback)

Listen for the active variant specified by an experiment.

  • Return Type: Subscription
  • Parameters:
    • experimentName - Name of an experiment. If provided, the callback will only be called for the specified experiment.
      • Optional
      • Type: string
      • Example: "My Example"
    • callback - Function to be called when a variant is chosen.
      • Required
      • Type: function
      • Callback Arguments:
        • experimentName - Name of the experiment.
          • Type: string
        • variantName - Name of the variant.
          • Type: string

emitter.addPlayListener([experimentName, ] callback)

Listen for an experiment being displayed to the user. Trigged by the React componentWillMount lifecycle method.

  • Return Type: Subscription
  • Parameters:
    • experimentName - Name of an experiment. If provided, the callback will only be called for the specified experiment.
      • Optional
      • Type: string
      • Example: "My Example"
    • callback - Function to be called when an experiment is displayed to the user.
      • Required
      • Type: function
      • Callback Arguments:
        • experimentName - Name of the experiment.
          • Type: string
        • variantName - Name of the variant.
          • Type: string

emitter.addWinListener([experimentName, ] callback)

Listen for a successful outcome from the experiment. Trigged by the emitter.emitWin(experimentName) method.

  • Return Type: Subscription
  • Parameters:
    • experimentName - Name of an experiment. If provided, the callback will only be called for the specified experiment.
      • Optional
      • Type: string
      • Example: "My Example"
    • callback - Function to be called when a win is emitted.
      • Required
      • Type: function
      • Callback Arguments:
        • experimentName - Name of the experiment.
          • Type: string
        • variantName - Name of the variant.
          • Type: string

emitter.defineVariants(experimentName, variantNames [, variantWeights])

Define experiment variant names and weighting. Required when an experiment spans multiple components containing different sets of variants.

If variantWeights are not specified variants will be chosen at equal rates.

The variants will be chosen according to the ratio of the numbers, for example variants ["A", "B", "C"] with weights [20, 40, 40] will be chosen 20%, 40%, and 40% of the time, respectively.

  • Return Type: No return value
  • Parameters:
    • experimentName - Name of the experiment.
      • Required
      • Type: string
      • Example: "My Example"
    • variantNames - Array of variant names.
      • Required
      • Type: Array.<string>
      • Example: ["A", "B", "C"]
    • variantWeights - Array of variant weights.
      • Optional
      • Type: Array.<number>
      • Example: [20, 40, 40]

emitter.setActiveVariant(experimentName, variantName)

Set the active variant of an experiment.

  • Return Type: No return value
  • Parameters:
    • experimentName - Name of the experiment.
      • Required
      • Type: string
      • Example: "My Example"
    • variantName - Name of the variant.
      • Required
      • Type: string
      • Example: "A"

emitter.getActiveVariant(experimentName)

Returns the variant name currently displayed by the experiment.

  • Return Type: string
  • Parameters:
    • experimentName - Name of the experiment.
      • Required
      • Type: string
      • Example: "My Example"

emitter.getSortedVariants(experimentName)

Returns a sorted array of variant names associated with the experiment.

  • Return Type: Array.<string>
  • Parameters:
    • experimentName - Name of the experiment.
      • Required
      • Type: string
      • Example: "My Example"

Subscription

Returned by the emitter's add listener methods. More information available in the facebook/emitter documentation.

subscription.remove()

Removes the listener subscription and prevents future callbacks.

  • Parameters: No parameters

experimentDebugger

Debugging tool. Attaches a fixed-position panel to the bottom of the <body> element that displays mounted experiments and enables the user to change active variants in real-time.

The debugger is wrapped in a conditional if(process.env.NODE_ENV === "production") {...} and will not display on production builds using envify.

experimentDebugger.enable()

Attaches the debugging panel to the <body> element.

  • Return Type: No return value

experimentDebugger.disable()

Removes the debugging panel from the <body> element.

  • Return Type: No return value

mixpanelHelper

Sends events to Mixpanel. Requires window.mixpanel to be set using Mixpanel's embed snippet.

Usage

When the <Experiment /> is mounted, the helper sends an Experiment Play event using mixpanel.track(...) with Experiment and Variant properties.

When a win is emitted the helper sends an Experiment Win event using mixpanel.track(...) with Experiment and Variant properties.

Try it on JSFiddle


var Experiment = require("react-ab-test/lib/Experiment");
var Variant = require("react-ab-test/lib/Variant");
var mixpanelHelper = require("react-ab-test/lib/helpers/mixpanel");

// window.mixpanel has been set by Mixpanel's embed snippet.
mixpanelHelper.enable();

var App = React.createClass({
  onButtonClick: function(e){
    emitter.emitWin("My Example");
    // mixpanelHelper sends the 'Experiment Win' event, equivalent to:
    // mixpanel.track('Experiment Win', {Experiment: "My Example", Variant: "A"})
  },
  componentWillMount: function(){
    // mixpanelHelper sends the 'Experiment Play' event, equivalent to:
    // mixpanel.track('Experiment Play', {Experiment: "My Example", Variant: "A"})
  },
  render: function(){
    return <div>
      <Experiment ref="experiment" name="My Example">
        <Variant name="A">
          <div>Section A</div>
        </Variant>
        <Variant name="B">
          <div>Section B</div>
        </Variant>
      </Experiment>
      <button onClick={this.onButtonClick}>Emit a win</button>
    </div>;
  }
});

mixpanelHelper.enable()

Add listeners to win and play events and report results to Mixpanel.

  • Return Type: No return value

mixpanelHelper.disable()

Remove win and play listeners and stop reporting results to Mixpanel.

  • Return Type: No return value

segmentHelper

Sends events to Segment. Requires window.segment to be set using Segment's embed snippet.

Usage

When the <Experiment /> is mounted, the helper sends an Experiment Play event using segment.track(...) with Experiment and Variant properties.

When a win is emitted the helper sends an Experiment Win event using segment.track(...) with Experiment and Variant properties.

Try it on JSFiddle


var Experiment = require("react-ab-test/lib/Experiment");
var Variant = require("react-ab-test/lib/Variant");
var segmentHelper = require("react-ab-test/lib/helpers/segment");

// window.segment has been set by Segment's embed snippet.
segmentHelper.enable();

var App = React.createClass({
  onButtonClick: function(e){
    emitter.emitWin("My Example");
    // segmentHelper sends the 'Experiment Win' event, equivalent to:
    // segment.track('Experiment Win', {Experiment: "My Example", Variant: "A"})
  },
  componentWillMount: function(){
    // segmentHelper sends the 'Experiment Play' event, equivalent to:
    // segment.track('Experiment Play', {Experiment: "My Example", Variant: "A"})
  },
  render: function(){
    return <div>
      <Experiment ref="experiment" name="My Example">
        <Variant name="A">
          <div>Section A</div>
        </Variant>
        <Variant name="B">
          <div>Section B</div>
        </Variant>
      </Experiment>
      <button onClick={this.onButtonClick}>Emit a win</button>
    </div>;
  }
});

segmentHelper.enable()

Add listeners to win and play events and report results to Segment.

  • Return Type: No return value

segmentHelper.disable()

Remove win and play listeners and stop reporting results to Segment.

  • Return Type: No return value

Tests

Browser Coverage

Karma tests are performed on Browserstack in the following browsers:

  • IE 9, Windows 7
  • IE 10, Windows 7
  • IE 11, Windows 7
  • Opera (latest version), Windows 7
  • Firefox (latest version), Windows 7
  • Chrome (latest version), Windows 7
  • Safari (latest version), OSX Yosemite
  • Android Browser (latest version), Google Nexus 7, Android 4.1
  • Mobile Safari (latest version), iPhone 6, iOS 8.3

Mocha tests are performed on the latest version of Node.

Please let us know if a different configuration should be included here.

Running Tests

Locally:


npm test

On Browserstack:


BROWSERSTACK_USERNAME=YOUR_USERNAME BROWSERSTACK_ACCESS_KEY=YOUR_ACCESS_KEY npm test