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.