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