react-ab-test
A/B Testing React Components
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!
Table of Contents
- Installation
-
Usage
<li> <a rel="nofollow noopener" target="_blank" href="#with-babel">With Babel</a> </li> </ul>
- Alternative Libraries
- Resources for A/B Testing with React
-
API Reference
-
<Experiment />
-
<Variant />
-
emitter
-
emitter.emitWin(experimentName)
-
emitter.addActiveVariantListener([experimentName, ] callback)
-
emitter.addPlayListener([experimentName, ] callback)
-
emitter.addWinListener([experimentName, ] callback)
-
emitter.defineVariants(experimentName, variantNames [, variantWeights])
-
emitter.setActiveVariant(experimentName, variantName)
-
emitter.getActiveVariant(experimentName)
-
emitter.getSortedVariants(experimentName)
-
<li> <a rel="nofollow noopener" target="_blank" href="#subscription"><code>Subscription</code></a></p> <ul dir="auto"> <li> <a rel="nofollow noopener" target="_blank" href="#subscriptionremove"><code>subscription.remove()</code></a> </li> </ul> </li> <li> <a rel="nofollow noopener" target="_blank" href="#experimentdebugger"><code>experimentDebugger</code></a></p> <ul dir="auto"> <li> <a rel="nofollow noopener" target="_blank" href="#experimentdebuggerenable"><code>experimentDebugger.enable()</code></a> </li> <li> <a rel="nofollow noopener" target="_blank" href="#experimentdebuggerdisable"><code>experimentDebugger.disable()</code></a> </li> </ul> </li> <li> <a rel="nofollow noopener" target="_blank" href="#mixpanelhelper"><code>mixpanelHelper</code></a></p> <ul dir="auto"> <li> <a rel="nofollow noopener" target="_blank" href="#usage-1">Usage</a> </li> <li> <a rel="nofollow noopener" target="_blank" href="#mixpanelhelperenable"><code>mixpanelHelper.enable()</code></a> </li> <li> <a rel="nofollow noopener" target="_blank" href="#mixpanelhelperdisable"><code>mixpanelHelper.disable()</code></a> </li> </ul> </li> <li> <a rel="nofollow noopener" target="_blank" href="#segmenthelper"><code>segmentHelper</code></a></p> <ul dir="auto"> <li> <a rel="nofollow noopener" target="_blank" href="#usage-2">Usage</a> </li> <li> <a rel="nofollow noopener" target="_blank" href="#segmenthelperenable"><code>segmentHelper.enable()</code></a> </li> <li> <a rel="nofollow noopener" target="_blank" href="#segmenthelperdisable"><code>segmentHelper.disable()</code></a> </li> </ul> </li> </ul>
-
- How to contribute
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 abEmitter = require("react-ab-test/lib/emitter") 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); abEmitter.rewind(); res.render('template', { sessionID: req.sessionID, reactOutput: reactString }); }); app.use(express.static('www')); app.listen(8080);
Remember to call abEmitter.rewind()
to prevent memory leaks.
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
- react-experiments – “A JavaScript library that assists in defining and managing UI experiments in React” by Hubspot. Uses Facebook’s PlanOut framework via Hubspot’s javascript port.
- react-ab – “Simple declarative and universal A/B testing component for React” by Ola Holmström
- react-native-ab – “A component for rendering A/B tests in React Native” by Loch Wansbrough
Please let us know about alternate libraries not included here.
Resources for A/B Testing with React
- Product Experimentation with React and PlanOut on the HubSpot Product Blog
- Roll Your Own A/B Tests With Optimizely and React on the Tilt Engineering Blog
- Simple Sequential A/B Testing
- A/B Testing Rigorously (without losing your job)
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"
<li> <code>userIdentifier</code> – Distinct user identifier. When defined, this value is hashed to choose a variant if…</code> </li> </ul>
-