├── Experiment.js
├── README.md
├── Tracking.js
├── Variant.js
├── index.js
└── package.json
/Experiment.js:
--------------------------------------------------------------------------------
1 | var React = require('react-native');
2 | var {
3 | AsyncStorage,
4 | PropTypes,
5 | View
6 | } = React;
7 |
8 | var Experiment = React.createClass({
9 |
10 | propTypes: {
11 | name: PropTypes.string.isRequired,
12 | children: ((props, propName) => {
13 | var children = props[propName];
14 | if (!Array.isArray(children) || children.length < 2) {
15 | return new Error('You must have at least 2 Variants.');
16 | }
17 | for (child of children) {
18 | if (!child.type.prototype.isVariant) {
19 | return new Error('One or more children is not a Variant.');
20 | }
21 | }
22 | }),
23 | choose: PropTypes.func,
24 | onChoice: PropTypes.func,
25 | onRawChoice: PropTypes.func
26 | },
27 |
28 | getDefaultProps() {
29 | return {
30 | choose(variants) {
31 | var choice = Math.floor(Math.random() * variants.length);
32 | return variants[choice];
33 | },
34 | onChoice(testName, variantName) { /* noop */ },
35 | onRawChoice(test, variant) { /* noop */ }
36 | }
37 | },
38 |
39 | getInitialState() {
40 | return {
41 | variant:
42 | }
43 | },
44 |
45 | componentWillMount() {
46 | this.variants = this.props.children;
47 |
48 | this.key = 'react-native-ab:Experiment:' + this.props.name;
49 |
50 | AsyncStorage.getItem(this.key, ((err, variantName) => {
51 | var choice;
52 | if (err || !variantName) {
53 | choice = this.props.choose(this.variants);
54 | AsyncStorage.setItem(this.key, choice.props.name); // Optimistic update
55 | }
56 | else {
57 | choice = this.getVariant(variantName);
58 | }
59 | this.props.onChoice(this.props.name, choice.props.name);
60 | this.props.onRawChoice(this, choice);
61 | this._onChange({
62 | variant: choice
63 | });
64 | }).bind(this));
65 | },
66 |
67 | render() {
68 | return this.state.variant;
69 | },
70 |
71 | getActiveVariant() {
72 | return this.state.variant;
73 | },
74 |
75 | getName() {
76 | return this.props.name;
77 | },
78 |
79 | getVariant(name) {
80 | return this.variants.find((v) => v.props.name == name);
81 | },
82 |
83 | reset(cb) {
84 | AsyncStorage.removeItem(this.key, cb);
85 | },
86 |
87 | _onChange(changed) {
88 | var newState = Object.assign({}, this.state, changed);
89 | this.setState(newState);
90 | }
91 | });
92 |
93 | module.exports = Experiment;
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-ab
2 |
3 | A component for rendering A/B tests in React Native.
4 |
5 | ## Getting started
6 |
7 | 1. `npm install react-native-ab@latest --save`
8 |
9 | ## Obtaining your metrics
10 |
11 | Once you've got your A/B tests set up, you'll need a place to send the data. Check out these libraries for A/B testing:
12 |
13 | - [Google Analytics (react-native-google-analytics)](http://github.com/lwansbrough/react-native-google-analytics)
14 |
15 | ## Usage
16 |
17 | All you need is to `require` the `react-native-ab` module and then use the provided tags (``, and ``).
18 |
19 | ```javascript
20 |
21 | var React = require('react-native');
22 | var {
23 | AppRegistry,
24 | StyleSheet,
25 | Text,
26 | View,
27 | TouchableHighlight
28 | } = React;
29 | var { Experiment, Variant } = require('react-native-ab');
30 |
31 | var rnabtest = React.createClass({
32 | render: function() {
33 | return (
34 |
35 |
36 |
37 | console.log(test, variant)}
41 | >
42 |
43 |
44 | Welcome to React Native!
45 |
46 |
47 |
48 |
49 | Hey there! Welcome to React Native!
50 |
51 |
52 |
53 |
54 | Howdy, partner! This here is React Native!
55 |
56 |
57 |
58 |
59 |
60 |
61 | To get started, edit index.ios.js
62 |
63 |
64 | Press Cmd+R to reload,{'\n'}
65 | Cmd+Control+Z for dev menu
66 |
67 |
68 | );
69 | },
70 |
71 | _resetExperiment() {
72 | this.refs.welcomeExperiment.reset();
73 | }
74 | });
75 |
76 | var styles = StyleSheet.create({
77 | container: {
78 | flex: 1,
79 | justifyContent: 'center',
80 | alignItems: 'center',
81 | backgroundColor: '#F5FCFF',
82 | },
83 | welcome: {
84 | fontSize: 20,
85 | textAlign: 'center',
86 | margin: 10,
87 | },
88 | instructions: {
89 | textAlign: 'center',
90 | color: '#333333',
91 | marginBottom: 5,
92 | },
93 | });
94 |
95 | AppRegistry.registerComponent('rnabtest', () => rnabtest);
96 | ```
97 |
98 | ## `` Properties
99 |
100 | #### `name`
101 |
102 | The name of your experiment, for example `"welcome-message"`.
103 |
104 |
105 | #### `choose`
106 |
107 | Example callback signature: `function(variants)`
108 | Must return an instance of `Variant`
109 |
110 | A function which should use a randomization algorithm to pick one of your variants. Defaults to a random selection using `Math.random()`
111 |
112 | #### `onChoice`
113 |
114 | Example callback signature: `function(experimentName, variantName)`
115 |
116 | Called when a `Variant` has been chosen for your `Experiment`.
117 |
118 | #### `onRawChoice`
119 |
120 | Example callback signature: `function(experiment, variant)`
121 |
122 | Same as `onChoice`, but the objects passed are React component instances.
123 |
124 | ## `` methods
125 |
126 | You can access component methods by adding a `ref` (ie. `ref="welcomeExperiment"`) prop to your `` element, then you can use `this.refs.welcomeExperiment.reset()`, etc. inside your component.
127 |
128 | #### `reset()`
129 |
130 | Resets the experiment. The next time the experiment is rendered, a new variant may be chosen.
131 |
132 | #### `getActiveVariant()`
133 |
134 | Returns the currently displayed `Variant`.
135 |
136 | #### `getVariant(variantName)`
137 |
138 | Returns the instance of the specified `Variant` name.
139 |
140 | ## `` Properties
141 |
142 | #### `name`
143 |
144 | The name of your variant, for example `"western"`.
145 |
146 | ## Subviews
147 | Only the `Variant` tag element exist within the `Experiment` element. `Variant` tags can contain only one *root* element.
148 |
--------------------------------------------------------------------------------
/Tracking.js:
--------------------------------------------------------------------------------
1 | var React = require('react-native');
2 | var AdSupportIOS = require('AdSupportIOS');
3 | var Dimensions = require('Dimensions');
4 |
5 | module.exports = {
6 | getAdvertisingId(cb) {
7 | AdSupportIOS.getAdvertisingId(function(advertisingId) {
8 | cb(null, advertisingId);
9 | }, cb);
10 | },
11 | device: {
12 | height: Dimensions.get('window').height,
13 | width: Dimensions.get('window').width
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/Variant.js:
--------------------------------------------------------------------------------
1 | var React = require('react-native');
2 | var { PropTypes } = React;
3 |
4 | var Variant = React.createClass({
5 | propTypes: {
6 | name: PropTypes.string.isRequired,
7 | children: PropTypes.element.isRequired
8 | },
9 |
10 | render() {
11 | return this.props.children;
12 | },
13 |
14 | getName() {
15 | return this.props.name;
16 | },
17 |
18 | isVariant: true
19 | });
20 |
21 | module.exports = Variant;
22 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Experiment: require('./Experiment'),
3 | Tracking: require('./Tracking'),
4 | Variant: require('./Variant')
5 | };
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-ab",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://github.com/lwansbrough/react-native-ab.git"
6 | },
7 | "version": "0.1.2",
8 | "description": "A component for rendering A/B tests in React Native",
9 | "main": "index.js",
10 | "author": "Lochlan Wansbrough (http://lwansbrough.com)",
11 | "peerDependencies": {
12 | "react-native": "*"
13 | },
14 | "keywords": [
15 | "react",
16 | "native",
17 | "ab",
18 | "test",
19 | "testing",
20 | "experiment",
21 | "variant"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------