Skip to content

Commit

Permalink
Add forced variation example (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptessier authored Jan 10, 2024
1 parent cc9f165 commit 4dedadb
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 19 deletions.
5 changes: 4 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
"@typescript-eslint/ban-types": "off",

// allow function overloads
"no-redeclare": "off"
"no-redeclare": "off",

// allow to force re-render hooks deps
"react-hooks/exhaustive-deps": "off"
}
}
20 changes: 13 additions & 7 deletions packages/react/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,27 @@ h1 {
line-height: 1.1;
}

input {
border-radius: 8px;
border: 1px solid #646cff;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
transition: all 250ms ease-out;
}

button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
background-color: #646cff;
cursor: pointer;
transition: border-color 0.25s;
transition: all 250ms ease-out;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
background-color: #646cffdd;
}
13 changes: 13 additions & 0 deletions packages/react/src/pages/home.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@
to {
transform: rotate(360deg);
}
}

.row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
margin: 1rem 0;
justify-content: center;
}

.row > input {
width: 8em;
}
68 changes: 60 additions & 8 deletions packages/react/src/pages/home.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,62 @@
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { getInstance } from "sdk";
import styles from "./home.module.css";
import mxLogo from "/mx.svg";
import reactLogo from "/react.svg";
import viteLogo from "/vite.svg";

export function HomePage() {
/**
* Force re-rendering of the component when forced variations or attributes changes.
* In a real world scenario, we wouldn't need to do this.
*/
const [renderCount, setRenderCount] = useState(0);
const [id, setId] = useState<string>("");

const assignment = useMemo(() => {
const experiments = getInstance();
return experiments.getAssignment("experiment-a");
}, []);
}, [renderCount]);

const attributes = useMemo(() => {
const experiments = getInstance();
return experiments.getAttributes();
}, []);
}, [renderCount]);

const experiments = useMemo(() => {
const experiments = getInstance();
const record = experiments.getExperiments();
return Object.values(record);
return experiments.getExperiments();
}, []);

const forcedVariations = useMemo(() => {
const experiments = getInstance();
return experiments.getForcedVariations();
}, [renderCount]);

const forceRender = () => {
setRenderCount((count) => count + 1);
};

const handleUpdateId = () => {
getInstance().setAttributes({ id });
forceRender();
};

const handleForceControl = () => {
getInstance().setForcedVariation("experiment-a", "control");
forceRender();
};

const handleForceTreatment = () => {
getInstance().setForcedVariation("experiment-a", "treatment");
forceRender();
};

const handleClearForcedVariations = () => {
getInstance().clearForcedVariations();
forceRender();
};

return (
<div className={styles.home}>
<div>
Expand All @@ -41,18 +76,35 @@ export function HomePage() {
<h1>A/B Testing on React</h1>
<div>
<h3>Experiments</h3>
{experiments.map((experiment) => (
<div key={experiment.key}>{JSON.stringify(experiment, null, 2)}</div>
))}
{JSON.stringify(Object.values(experiments))}
</div>
<div>
<h3>Subject Attributes</h3>
{JSON.stringify(attributes, null, 2)}
<div className={styles.row}>
<input
type="input"
placeholder="id"
value={id}
onChange={(e) => setId(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleUpdateId()}
/>
<button onClick={handleUpdateId}>Update</button>
</div>
</div>
<div>
<h3>Assignment</h3>
{JSON.stringify(assignment, null, 2)}
</div>
<div>
<h3>Forced Variations</h3>
{JSON.stringify(forcedVariations, null, 2)}
<div className={styles.row}>
<button onClick={handleForceControl}>Force control</button>
<button onClick={handleForceTreatment}>Force treatment</button>
<button onClick={handleClearForcedVariations}>Clear</button>
</div>
</div>
</div>
);
}
10 changes: 7 additions & 3 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ export class Client {
}

public getForcedVariations() {
this.store.forcedVariations.getEntries();
return this.store.forcedVariations.getEntries();
}

public setForcedVariations(experimentKey: string, variationKey: string) {
public setForcedVariation(experimentKey: string, variationKey: string) {
this.store.forcedVariations.set(experimentKey, variationKey);
}

public clearForcedVariations() {
this.store.forcedVariations.clear();
}

public getAssignment(experimentKey: string): IAssignment {
// Exclude if experiment has no key
if (experimentKey === null || experimentKey === undefined || experimentKey === "") {
Expand Down Expand Up @@ -197,5 +201,5 @@ export class Client {
function getTrackingKey(props: IAssignmentEvent): string {
const { experimentKey, variationKey, subjectAttribute, subjectKey } = props;

return [experimentKey, variationKey, stringify(subjectAttribute), subjectKey].filter((value) => !!value).join(":");
return [experimentKey, variationKey, stringify(subjectAttribute), subjectKey].join(":");
}

0 comments on commit 4dedadb

Please sign in to comment.