Growth HOC-ing — Running A/B tests with React/Redux and a Higher Order Component (HOC)

Tim Kutnick
2 min readJun 11, 2018

--

So you want to run an A/B test? Using React & Redux? Sweet, this is for you.

Code here: https://github.com/kutnickclose/growth-hoc

EXAMPLE — Copy Change Experiment

Let’s say we want to run a copy change for a sign up button. The Control will say “Sign Up” and the Variation will say “Continue”

// home/index.jsximport React, { Component } from 'react';class Home extends Component {
render() {
return (
<div>
<button>
Sign Up
</button>
</div>
);
}
}
export default Home;

Let’s now conditionally render “Continue” if we’re showing the variation.

// home/index.jsximport React, { Component } from 'react';class Home extends Component {
render() {
return (
<div>
<button>
{this.props.showVariation ? "Continue" : "Sign Up"}
</button>
</div>
);
}
}
export default Home;

Great, but where does this.props.showVariation come from? It’s going to come from a Higher Order Component (HOC). Let’s add it!

// sign_up/index.jsximport React, { Component } from 'react';
import withExperiment from '../../utils/experiment.js';
class Home extends Component {
render() {
return (
<div>
<button>
{this.props.showVariation ? "Continue" : "Sign Up"}
</button>
</div>
);
}
}
export default withExperiment('signUpCopyChange', Home)

What is a HOC? From the React Documentation:

A higher-order component is a function that takes a component and returns a new component

Great. All we’re going to do is pass a component (and experiment name) to the HOC and let the HOC handle everything else.

// utils/experiment.js
import startExperiment from '../actions/experiment';
import React from 'react';
import { connect } from 'react-redux';
const withExperiment = (experimentName, WrappedComponent) => {
class Experiment extends React.Component {
// start the experiment (or continue if already in experiment)
// this will do the following
// 1) make an API request to the server asking for
// which variation the user is in
// 2) set Redux Store state to something like
// { copyTest: 'control' }
componentDidMount() {
this.props.startExperiment(experimentName);
}
// if we haven't fetched experiment yet, render nothing
// otherwise render the component with the original
// props and the showVariation prop included
render() {
const { [experimentName]: variation, ...rest } = this.props;
if (variation === undefined) return null;
return <WrappedComponent {...rest} />;
}
}

// add showVariation and the experimentName to props
const mapStateToProps = state => ({
showVariation: state.experiments[experimentName] !== 'control',
[experimentName]: state.experiments[experimentName],
});

const mapDispatchToProps = dispatch => ({
startExperiment: name => dispatch(startExperiment(name)),
});

return connect(mapStateToProps, mapDispatchToProps)(Experiment);
}
export default withExperiment;

What? Basically all we’re doing is taking the component we passed to withExperiment, adding a prop to it with a key of showVariation and a value of true or false and returning the component with this added prop.

And that’s it! The experiment HOC can now be a one stop shop for adding additional functionality (rendering THIS or THAT component, handling various responses from the server about the experiment and which variation the user should see, adding integrations with analytic trackers, etc).

--

--