WeExperiment React
React helpers for we-experiment-js.
Installation
Install using NPM:
npm install --save @wework/we-experiment-react
Usage
The main use-case for this library is using a Redux store.
Redux Setup
This library requires that an experiments reducer be mounted at the top-level of your store, under the key experiments.
A helper is provided for creating a reducer for the experiments, which is setup like so:
import { combineReducers, createStore } from 'redux';
import { combineExperiments } from 'we-experiment-react';
import reducers from 'reducers';
const withExperimentsReducer = combineExperiments(reducers);
const rootReducer = combineReducers(withExperimentsReducer);
const store = createStore(rootReducer);
Alternatively, you can import the reducer directly and use it as you would normally:
import { combineReducers, createStore } from 'redux';
import { reducer as experimentsReducer } from 'we-experiment-react';
const rootReducer = {
experiments: experimentsReducer,
}
const store = createStore(rootReducer)
Fetch experiments
Experiments may be hydrated manually or fetched using a helper function which in turn uses we-experiment-js.
import { initExperiments, fetchExperiments } from 'we-experiment-react';
const experiments = {
foo: 'control',
bar: 'variant_1',
};
store.dispatch(initExperiments(experiments));
const experimentNames = ['EXPERIMENT_1', 'EXPERIMENT_2'];
store.dispatch(fetchExperiments('USER_ID', 'SPLIT_API_KEY', experimentNames ));
Note that in order to use fetchExperiments, support for Promises is required for the Redux setup, for example, using redux-thunk.
Alternatively, it may be run like so:
fetchExperiments('USER_ID', 'SPLIT_API_KEY', experimentNames )(store.dispatch);
See we-experiment-js's documentation for details on the available options.
Update experiments
It's possible to update the experiments fetched using fetchExperiments, using refreshExperiments.
The refreshExperiments function should be called only after calling fetchExperiments.
This function allows you to fetch new experiments or update existing ones, passing attributes that
will be added to or override the ones passed on the call to fetchExperiments.
If any existing experiment names are passed, their value will be reset until the actual results are fetched.
import { fetchExperiments, refreshExperiments } from 'we-experiment-react';
const experimentNames = ['EXPERIMENT_1', 'EXPERIMENT_2'];
store.dispatch(fetchExperiments('USER_ID', 'SPLIT_API_KEY', experimentNames ));
const experimentsToUpdate = ['EXPERIMENT_2', 'EXPERIMENT_3'];
const attributes = {};
store.dispatch(refreshExperiments(experimentsToUpdate, attributes));
Experiment Component
An Experiment component is provided for use-cases where a simple component replacement is required:
import { Experiment, Control, Variant } from 'we-experiment-react';
const experiment = (
<Experiment name="EXPERIMENT_1">
<Control>
<Foo />
</Control>
<Variant name="variant_1">
<Bar />
</Variant>
</Experiment>
);
ReactDOM.render(experiment, document.getElementById('container'));
Alternatively, Experiment may receive a render prop as its child:
<Experiment name="EXPERIMENT_1">
{
(variant) => variant === 'on' ? <Foo /> : <Bar />
}
</Experiment>
Experiment Higher-Order Component
We provide 2 HOC's to retrieve experiments from store connectExperiment and connectExperiments
connectExperiment
import React from 'react';
import { connectExperiment } from 'we-experiment-react';
class Foo extends React.Component {
render() {
const variant = this.props.exp;
if (!variant) {
return null;
}
switch (variant) {
case 'control':
return <Bar />;
case 'variant_1':
return <Baz />;
}
}
}
Foo.propTypes = {
exp: PropTypes.string,
};
export default connectExperiment('exp')(Foo);
By default, the variant will be passed as a prop with the same name as the experiment.
It is also possible to pass a propName option to change the prop's name:
export default connectExperiment('exp', { propName: 'woot' })(Foo);
connectExperiments
import React from 'react';
import { connectExperiments } from 'we-experiment-react';
class Foo extends React.Component {
render() {
const {
exp1,
exp2,
} = this.props;
if (exp1 === 'on' && exp2 === 'on') {
return <div>Experiments are 'on'</div>;
}
return <div>Experiments are 'off'</div>;
}
}
export default connectExperiments({
exp1: true,
exp2: true,
})(Foo);
By default, the variant will be passed as a prop with the same name as the experiment.
It is also possible to pass a prop name as value or propName option in object to change the prop's name:
export default connectExperiments({
exp1: 'expX',
exp2: {
propName: 'expZ',
},
})(Foo);
FeatureFlag Component
A FeatureFlag component is provided for simpler use-cases where only an on/off experiment exists.
This component will display its children only if the provided experiment's group equals to the activeValue prop's
value (default: "on").
<FeatureFlag name="FEATURE_2">
<Foo />
</FeatureFlag>
Feature HOC
In addition to the above component, a matching feature HOC is provided to allow same behavior at the component level.
export default feature('exp')(Foo);
This HOC also accepts an object as its optional second argument, with an activeValue key (default: "on") which
behaves the same as the above component's prop of the same name.