Skip to content
This repository has been archived by the owner on Sep 1, 2022. It is now read-only.

Commit

Permalink
Code refactor for easier maintenance. Remove extraneous console logs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jdorn committed Apr 8, 2021
1 parent e2e79c0 commit 92ae6dd
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 112 deletions.
49 changes: 27 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Powerful A/B testing for React.
![Build Status](https://github.com/growthbook/growthbook-react/workflows/Build/badge.svg)

- **No external dependencies**
- **Lightweight and fast** (4Kb gzipped)
- **Lightweight and fast** (2.6Kb gzipped)
- **No HTTP requests** everything is defined and evaluated locally
- Works for both **client and server-side** rendering
- **Dev Mode** for testing variations and taking screenshots
Expand Down Expand Up @@ -57,7 +57,7 @@ export default function App() {
}
```

Step 2a: Run experiments! (with React Hooks)
Step 2: Run experiments!

```tsx
import {useExperiment} from '@growthbook/growthbook-react';
Expand All @@ -72,26 +72,7 @@ export default function OtherComponent() {
}
```

Step 2b: Run experiments! (with Class Components)

```tsx
import {withRunExperiment} from '@growthbook/growthbook-react';

class MyComponent extends Component {
render() {
// The `runExperiment` prop is identical to the `useExperiment` hook
const {value} = this.props.runExperiment({
key: "headline-test",
variations: ["Hello World", "Hola Mundo"]
});

return <h1>{value}</h1>
}
}

// Wrap your component in `withRunExperiment`
export default withRunExperiment(MyComponent);
```
Use class components? We support that too! [See Example](#react-class-components)

## Dev Mode

Expand Down Expand Up @@ -357,3 +338,27 @@ Integration is super easy:
3. At the start of your app, run `client.importOverrides(listFromCache)`

Now you can start/stop tests, adjust coverage and variation weights, and apply a winning variation to 100% of traffic, all within the Growth Book App without deploying code changes to your site.


## React Class Components

If you aren't using functional components, we offer a `withRunExperiment` Higher Order Component instead.

```tsx
import {withRunExperiment} from '@growthbook/growthbook-react';

class MyComponent extends Component {
render() {
// The `runExperiment` prop is identical to the `useExperiment` hook
const {value} = this.props.runExperiment({
key: "headline-test",
variations: ["Hello World", "Hola Mundo"]
});

return <h1>{value}</h1>
}
}

// Wrap your component in `withRunExperiment`
export default withRunExperiment(MyComponent);
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@
"size-limit": [
{
"path": "dist/growthbook-react.cjs.production.min.js",
"limit": "10 KB"
"limit": "4 KB"
},
{
"path": "dist/growthbook-react.esm.js",
"limit": "10 KB"
"limit": "4 KB"
}
],
"devDependencies": {
Expand Down
5 changes: 3 additions & 2 deletions src/dev/VariationSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ export default function VariationSwitcher({
h,
});
})
.catch((e) => {
console.error(e);
// This usually means the user clicked cancel on the
// screen sharing prompt
.catch(() => {
setScreenshotData(null);
});
}, [screenshotExperiment, forceVariation, screenshotNumVariations]);
Expand Down
7 changes: 0 additions & 7 deletions src/dev/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ export function captureScreenshots(
numVariations: number,
forceVariation: (i: number) => void
): Promise<{ capturedImages: string[]; w: number; h: number }> {
console.log('Getting capture stream', numVariations);

return (navigator.mediaDevices as any)
.getDisplayMedia({
video: {
Expand All @@ -19,7 +17,6 @@ export function captureScreenshots(
canvas.width = settings.width || 1024;
canvas.height = settings.height || 768;
const ctx = canvas.getContext('2d');
console.log('video playing', ctx);
if (!ctx) {
reject(new Error('Could not get canvas context'));
return;
Expand All @@ -44,14 +41,11 @@ export function captureScreenshots(
for (let i = 0; i < numVariations; i++) {
result = result
.then(() => {
console.log('Forcing variation ', i);
forceVariation(i);
return new Promise((resolve) => setTimeout(resolve, 200));
})
.then(() => {
ctx.drawImage(video, 0, 0);
console.log('captured variation image', i);

return new Promise<string>((resolve) => {
canvas.toBlob((b) => {
resolve(URL.createObjectURL(b));
Expand Down Expand Up @@ -87,7 +81,6 @@ export function captureScreenshots(
}
};
video.srcObject = captureStream;
console.log('started video', settings);
});
});
}
Expand Down
89 changes: 10 additions & 79 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type {
} from '@growthbook/growthbook/dist/types';
import type GrowthBookUser from '@growthbook/growthbook/dist/user';
import VariationSwitcher from './dev/VariationSwitcher';
import { useForceVariation } from './useForceVariation';
import { runExperiment } from './runExperiment';

export { default as GrowthBookClient } from '@growthbook/growthbook';
export type { default as GrowthBookUser } from '@growthbook/growthbook/dist/user';
Expand All @@ -16,38 +18,22 @@ export type {
ClientConfigInterface,
} from '@growthbook/growthbook/dist/types';

const SESSION_STORAGE_KEY = 'gb_forced_variations';

export const GrowthBookContext = React.createContext<{
export type GrowthBookContextValue = {
user: GrowthBookUser | null;
}>({ user: null });

function runExperiment<T>(
user: GrowthBookUser | null,
exp: Experiment<T>
): ExperimentResults<T> {
if (user) {
return user.experiment(exp);
}

return {
experiment: exp,
variationId: 0,
inExperiment: false,
index: 0,
value: exp.variations[0],
};
};
export interface WithRunExperimentProps {
runExperiment: <T>(exp: Experiment<T>) => ExperimentResults<T>;
}

export const GrowthBookContext = React.createContext<GrowthBookContextValue>({
user: null,
});

export function useExperiment<T>(exp: Experiment<T>): ExperimentResults<T> {
const { user } = React.useContext(GrowthBookContext);
return runExperiment(user, exp);
}

export interface WithRunExperimentProps {
runExperiment: <T>(exp: Experiment<T>) => ExperimentResults<T>;
}

export const withRunExperiment = <P extends WithRunExperimentProps>(
Component: React.ComponentType<P>
): React.ComponentType<Omit<P, keyof WithRunExperimentProps>> => (
Expand All @@ -65,61 +51,6 @@ export const withRunExperiment = <P extends WithRunExperimentProps>(
</GrowthBookContext.Consumer>
);

function useForceVariation(
user: GrowthBookUser | null
): {
forceVariation: (key: string, variation: number) => void;
renderCount: number;
} {
const [init, setInit] = React.useState(false);
const [renderCount, setRenderCount] = React.useState(1);

React.useEffect(() => {
if (!user || init) return;
setInit(true);
if (process.env.NODE_ENV === 'production') {
return;
}
try {
let forced = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
if (forced) {
let json = JSON.parse(forced);
Object.keys(json).forEach((key) => {
user?.client.forcedVariations.set(key, json[key]);
});
setRenderCount((i) => i + 1);
}
} catch (e) {
// Ignore sessionStorage errors
}
}, [user, init]);

const forceVariation = /*#__PURE__*/ React.useCallback(
(key: string, variation: number) => {
if (!user) return;
user.client.forcedVariations.set(key, variation);
setRenderCount((i) => i + 1);
try {
let forced = window.sessionStorage.getItem(SESSION_STORAGE_KEY) || '{}';
let json = JSON.parse(forced);
json[key] = variation;
window.sessionStorage.setItem(
SESSION_STORAGE_KEY,
JSON.stringify(json)
);
} catch (e) {
// Ignore sessionStorage errors
}
},
[user]
);

return {
renderCount,
forceVariation,
};
}

export const GrowthBookProvider: React.FC<{
user?: GrowthBookUser | null;
disableDevMode?: boolean;
Expand Down
22 changes: 22 additions & 0 deletions src/runExperiment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type {
Experiment,
ExperimentResults,
} from '@growthbook/growthbook/dist/types';
import type { GrowthBookUser } from '.';

export function runExperiment<T>(
user: GrowthBookUser | null,
exp: Experiment<T>
): ExperimentResults<T> {
if (user) {
return user.experiment(exp);
}

return {
experiment: exp,
variationId: 0,
inExperiment: false,
index: 0,
value: exp.variations[0],
};
}
59 changes: 59 additions & 0 deletions src/useForceVariation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react';
import { GrowthBookUser } from '.';

const SESSION_STORAGE_KEY = 'gb_forced_variations';

export function useForceVariation(
user: GrowthBookUser | null
): {
forceVariation: (key: string, variation: number) => void;
renderCount: number;
} {
const [init, setInit] = React.useState(false);
const [renderCount, setRenderCount] = React.useState(1);

React.useEffect(() => {
if (!user || init) return;
setInit(true);
if (process.env.NODE_ENV === 'production') {
return;
}
try {
let forced = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
if (forced) {
let json = JSON.parse(forced);
Object.keys(json).forEach((key) => {
user?.client.forcedVariations.set(key, json[key]);
});
setRenderCount((i) => i + 1);
}
} catch (e) {
// Ignore sessionStorage errors
}
}, [user, init]);

const forceVariation = /*#__PURE__*/ React.useCallback(
(key: string, variation: number) => {
if (!user) return;
user.client.forcedVariations.set(key, variation);
setRenderCount((i) => i + 1);
try {
let forced = window.sessionStorage.getItem(SESSION_STORAGE_KEY) || '{}';
let json = JSON.parse(forced);
json[key] = variation;
window.sessionStorage.setItem(
SESSION_STORAGE_KEY,
JSON.stringify(json)
);
} catch (e) {
// Ignore sessionStorage errors
}
},
[user]
);

return {
renderCount,
forceVariation,
};
}

0 comments on commit 92ae6dd

Please sign in to comment.