├── 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 | --------------------------------------------------------------------------------