Skip to content
Permalink
Browse files

feat: increase test flexibility with new exports; ExperimentProvider,…

… ExperimentConsumer, withExperiments, ActiveVariant

BREAKING CHANGE: requires React 16.3+
BREAKING CHANGE: removal of `ExperimentManager`
BREAKING CHANGE: removal of `ExperimentQueryString`
  • Loading branch information...
danhayden committed May 25, 2018
1 parent 4379df0 commit 4d704f7d72eb0c7035f56a42cc29117c822ffc46
@@ -0,0 +1,20 @@
{
"presets": [
[
"babel-preset-env",
{
"targets": {
"browsers": [
"last 2 Chrome versions",
"last 2 Edge versions",
"last 2 Firefox versions",
"last 2 Safari versions",
"Explorer 11"
]
}
}
],
"babel-preset-react",
"babel-preset-stage-2"
]
}
@@ -12,6 +12,15 @@ module.exports = {
node: true
},
globals: {
Promise: true
before: true,
beforeEach: true,
after: true,
afterEach: true,
describe: true,
expect: true,
it: true,
jest: true,
Promise: true,
test: true
}
};
@@ -0,0 +1 @@
package.json

This file was deleted.

Oops, something went wrong.
268 README.md
@@ -8,170 +8,175 @@

Simple A/B testing for React

## Usage
- Exports `ExperimentConsumer` [Render Prop] and `withExperiments` [HOC] for accessing and modifying experiments
- Persists assigned variant for return visits (using [localForage])
- configurable variants weights
- **Requires React 16.3+** due to utilising new [context]

### Experiment & Variant
## Exports

```js
import React from "react";
import { Experiment, Variant } from "react-simple-experiment";
export default function MyABTest() {
return (
<Experiment
name="Experiment Name"
onLoad={(name, variant) => console.log(name, variant)}
>
<Variant name="Variant 1 Name" weight={60}>
<div>Variant 1</div>
</Variant>
<Variant name="Variant 2 Name" weight={20}>
<div>Variant 2</div>
</Variant>
<Variant name="Variant 3 Name" weight={20}>
<div>Variant 3</div>
</Variant>
</Experiment>
);
}
```
- `ExperimentProvider`
- `ExperimentConsumer`
- `withExperiments`
- `Experiment`
- `Variant`
- `ActiveVariant`

Once a variant has been chosen the choice is persisted to browser storage (using [localForage]) to ensure the same variant is shown on return visits

### ExperimentQueryString

If you use react-router-dom in your project `<ExperimentQueryString />` component allows you can set experimant variants using a query string, like so:
## Usage

Experiment query strings can now be formatted as below:
`?exp=experimentId1||variantId&exp=experimentId2||variantId`
### ExperimentProvider, Experiment & Variant

```js
import { ExperimentQueryString } from "react-simple-experiment";
import { Route } from "react-router-dom";
import React, { Component } from "react";
import { render } from "react-dom";
import {
ExperimentProvider,
Experiment,
Variant
} from "react-simple-experiment";
class MyExperiment extends Component {
logExperimentInfo = ({ name, variants, activeVariantId }) => {
console.log({ name, variants, activeVariantId });
};
<Route component={ExperimentQueryString} />;
```
render() {
return (
<Experiment name="Experiment Name" onLoad={this.logExperimentInfo}>
<Variant name="control" weight={60}>
<div>Control</div>
</Variant>
<Variant name="variant1" weight={20}>
<div>Variant 1</div>
</Variant>
</Experiment>
);
}
}
you can customise `querystringName` and `querystringDivider` using props:
class App extends Component {
render() {
return (
<ExperimentProvider>
<MyExperiment />
</ExperimentProvider>
);
}
}
```js
import { ExperimentQueryString } from "react-simple-experiment";
import { Route } from "react-router-dom";
<Route
render={({ location }) => (
<ExperimentQueryString
querystringName="variant"
querystringDivider="__"
location={location}
/>
)}
/>;
render(<App />, document.getElementById("root"));
```

Experiment query strings can now be formatted as below:
`?variant=experimentId1__variantId&variant=experimentId2__variantId`

### ExperimentManager

If you'd like to manipulate active experiment varaints within a component, perhaps for debugging, you can use the `<ExperimentManager/ >` component which takes a function for its children prop.
The render function exposes the following four functions:
### ExperimentProvider, ActiveVariant & withExperiments

```js
getExperiments().then(experiments => {
console.log(experiments);
/*
{
"experimentId1": {
"activeVariant": "control",
"variants": {
"control": 50,
"variant1": 50
}
},
"experimentId2": {
"activeVariant": "variant1",
"variants": {
"control": 50,
"variant1": 50
}
import React, { Component } from "react";
import { render } from "react-dom";
import {
ExperimentProvider,
ActiveVariant,
withExperiments
} from "react-simple-experiment";
class MyExperimentBase extends Component {
componentDidMount() {
this.props.createExperiment({
onLoad: this.logExperimentInfo,
name: "Experiment Name",
variants: {
control: 1, // integer represents variant weight
variant: 1
}
}
*/
});
getExperiment(experimentId).then(variantId => {
console.log(variantId);
// variantId
});
});
}
setExperiment(experimentId, variantId).then(variantId => {
console.log(variantId);
// variantId
});
logExperimentInfo = ({ name, variants, activeVariantId }) => {
console.log({ name, variants, activeVariantId });
};
removeExperiment(experimentId).then(undefined => {});
```
render() {
return (
<div>
<ActiveVariant experiment="Experiment Name" variant="control">
<div>Control</div>
</ActiveVariant>
<ActiveVariant experiment="Experiment Name" variant="variant">
<div>Variant 1</div>
</ActiveVariant>
</div>
);
}
}
and can be used within a component as a render prop like so:
const MyExperiment = withExperiments(MyExperimentBase);
```js
import { ExperimentManager } from "react-simple-experiment";
export default function MyExperimentManager() {
return (
<ExperimentManager
render={({
getExperiments,
getExperiment,
setExperiment,
removeExperiment
}) => {
return /* custom logic.. */;
}}
/>
);
class App extends Component {
render() {
return (
<ExperimentProvider>
<MyExperiment />
</ExperimentProvider>
);
}
}
render(<App />, document.getElementById("root"));
```

or you can pass it a component to enable using lifecycle methods:
### ExperimentProvider, ExperimentConsumer

```js
import { ExperimentManager } from "react-simple-experiment";
export default function MyExperimentManager() {
state = {
experiments: null
import React, { Component } from "react";
import { render } from "react-dom";
import {
ExperimentProvider,
ExperimentConsumer
} from "react-simple-experiment";
class MyExperimentManagerBase extends Component {
setControl = () => {
this.props.setExperimentVariant("Experiment Name", "control");
};
componentDidMount() {
this.getExperiments();
}
getExperiments = (reload = false) => {
this.props.getExperiments().then(experiments => {
this.setState(() => ({ experiments })
);
});
setVariant = () => {
this.props.setExperimentVariant("Experiment Name", "variant");
};
render () {
render() {
return (
<div>
{JSON.stringify(this.state.experiments, null, 2)}
<button onClick={this.setControl}>set control</button>
<button onClick={this.setVariant}>set variant</button>
</div>
);
};
}
}
export default props => (
<ExperimentManager
location={props.location}
component={MyExperimentManager}
{...props} />
const MyExperimentManager = props => (
<ExperimentConsumer>
{context => (
<MyExperimentManagerBase
{...props}
experiments={context.experiments}
createExperiment={context.createExperiment}
removeExperiment={context.removeExperiment}
setExperimentVariant={context.setExperimentVariant}
/>
)}
</ExperimentConsumer>
);
class App extends Component {
render() {
return (
<ExperimentProvider>
<MyExperimentManager />
</ExperimentProvider>
);
}
}
render(<App />, document.getElementById("root"));
```

### License
@@ -184,3 +189,6 @@ MIT
[size]: https://badges.herokuapp.com/size/npm/react-simple-experiment
[size gzip]: https://badges.herokuapp.com/size/npm/react-simple-experiment?gzip=true
[localforage]: https://github.com/localForage/localForage
[render prop]: https://reactjs.org/docs/render-props.html
[hoc]: https://reactjs.org/docs/higher-order-components.html
[context]: https://reactjs.org/docs/context.html
BIN -7.1 KB docs/favicon.ico
Binary file not shown.

This file was deleted.

Oops, something went wrong.
Oops, something went wrong.

0 comments on commit 4d704f7

Please sign in to comment.
You can’t perform that action at this time.