@the-control-group/react-split-test
Advanced tools
Comparing version 0.9.1 to 1.0.0
import React, { Component } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
const variationPromiseCache = new Map(); | ||
export default class Experiment extends Component { | ||
@@ -9,7 +11,21 @@ static propTypes = { | ||
session: PropTypes.bool, | ||
onParticipate: PropTypes.func | ||
onParticipate: PropTypes.func, | ||
variationDecider: PropTypes.func | ||
}; | ||
static defaultProps = { | ||
onParticipate: () => {} | ||
onParticipate: () => {}, | ||
/** | ||
* Stub A/B decider | ||
* @param {React.node} experimentChildren - React children nodes of the experiment | ||
* @return {Promise<variation id>} Selected variation ID | ||
*/ | ||
variationDecider: experimentChildren => { | ||
const variations = []; | ||
React.Children.forEach(experimentChildren, c => { | ||
if(c.props.isVariation) variations.push(c.props.id); | ||
}); | ||
return Promise.resolve(variations[Math.floor(Math.random() * variations.length)]); | ||
} | ||
}; | ||
@@ -36,24 +52,44 @@ | ||
let selectedVariation; | ||
if(cachedData) { | ||
selectedVariation = cachedData.selectedVariation; | ||
return cachedData.selectedVariation; | ||
} else { | ||
const variations = []; | ||
React.Children.forEach(this.props.children, c => { | ||
if(c.props.isVariation) variations.push(c.props.id); | ||
}); | ||
// Cache promises so only one decision is made per experiment ID | ||
let variationDecider; | ||
if(variationPromiseCache.has(this.props.id)) { | ||
variationDecider = variationPromiseCache.get(this.props.id); | ||
} else { | ||
variationDecider = this.props.variationDecider(this.props.children); | ||
variationPromiseCache.set(this.props.id, variationDecider); | ||
} | ||
selectedVariation = variations[Math.floor(Math.random() * variations.length)]; | ||
variationDecider | ||
.then(selectedVariation => { | ||
const experimentData = { | ||
id: this.props.id, | ||
selectedVariation | ||
}; | ||
const experimentData = { | ||
id: this.props.id, | ||
selectedVariation | ||
}; | ||
// Since this is async, we need to check if another component with the same experiment | ||
// has already made this decision in a potential race condition | ||
let raceData; | ||
try { | ||
raceData = JSON.parse(this.storage.getItem(this.cacheKey)); | ||
} catch(e) { | ||
// No/invalid data | ||
} | ||
this.props.onParticipate(experimentData); | ||
if(!raceData) { | ||
this.storage.setItem(this.cacheKey, JSON.stringify(experimentData)); | ||
this.props.onParticipate(experimentData); | ||
// Remove the initial promise from the cache since subsequent renders/mounts will work off of the cache | ||
variationPromiseCache.delete(this.props.id); | ||
} | ||
this.storage.setItem(this.cacheKey, JSON.stringify(experimentData)); | ||
this.setState({ | ||
selectedVariation: raceData ? raceData.selectedVariation : selectedVariation | ||
}); | ||
}); | ||
return null; | ||
} | ||
return selectedVariation; | ||
} | ||
@@ -60,0 +96,0 @@ |
export { default as Experiment } from './Experiment'; | ||
export { default as Variation } from './Variation'; | ||
export function clearExperimentCache() { | ||
// These only work for persisted variations | ||
export function getCachedVariation(id) { | ||
return JSON.parse(localStorage.getItem(`@tcg-split-test:${id}`)); | ||
} | ||
export function clearExperimentCache(id) { | ||
localStorage.removeItem(`@tcg-split-test:${id}`); | ||
} | ||
export function clearAllExperimentCaches() { | ||
Object.keys(localStorage).forEach(k => /^@tcg-split-test:/.test(k) && localStorage.removeItem(k)); | ||
} |
{ | ||
"name": "@the-control-group/react-split-test", | ||
"version": "0.9.1", | ||
"version": "1.0.0", | ||
"description": "A/B Split Testing Component for React", | ||
@@ -34,20 +34,20 @@ "files": [ | ||
"devDependencies": { | ||
"@babel/cli": "^7.0.0-rc.1", | ||
"@babel/core": "^7.0.0-rc.1", | ||
"@babel/plugin-proposal-class-properties": "^7.0.0-rc.1", | ||
"@babel/preset-env": "^7.0.0-rc.1", | ||
"@babel/preset-react": "^7.0.0-rc.1", | ||
"babel-eslint": "^8.2.6", | ||
"babel-loader": "^8.0.0-beta.4", | ||
"@babel/cli": "^7.6.4", | ||
"@babel/core": "^7.6.4", | ||
"@babel/plugin-proposal-class-properties": "^7.5.5", | ||
"@babel/preset-env": "^7.6.3", | ||
"@babel/preset-react": "^7.6.3", | ||
"babel-eslint": "^10.0.3", | ||
"babel-loader": "^8.0.6", | ||
"cpx": "^1.5.0", | ||
"eslint": "^5.3.0", | ||
"eslint-plugin-react": "^7.10.0", | ||
"eslint": "^6.5.1", | ||
"eslint-plugin-react": "^7.16.0", | ||
"html-webpack-plugin": "^3.2.0", | ||
"prop-types": "^15.6.2", | ||
"react": "^16.4.2", | ||
"react-devtools": "^3.2.3", | ||
"react-dom": "^16.4.2", | ||
"webpack": "^4.16.5", | ||
"webpack-cli": "^3.1.0", | ||
"webpack-dev-server": "^3.1.5" | ||
"react-devtools": "^4.2.0", | ||
"react-dom": "^16.10.2", | ||
"webpack": "^4.41.0", | ||
"webpack-cli": "^3.3.9", | ||
"webpack-dev-server": "^3.8.2" | ||
}, | ||
@@ -54,0 +54,0 @@ "peerDependencies": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
5821
113
0