diff --git a/.eslintrc b/.eslintrc index 699c84d..cdc27fa 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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" } } diff --git a/packages/react/src/index.css b/packages/react/src/index.css index f3d8bd8..31a8493 100644 --- a/packages/react/src/index.css +++ b/packages/react/src/index.css @@ -49,6 +49,16 @@ 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; @@ -56,14 +66,10 @@ button { 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; } diff --git a/packages/react/src/pages/home.module.css b/packages/react/src/pages/home.module.css index 508da8c..d68d143 100644 --- a/packages/react/src/pages/home.module.css +++ b/packages/react/src/pages/home.module.css @@ -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; } \ No newline at end of file diff --git a/packages/react/src/pages/home.tsx b/packages/react/src/pages/home.tsx index 39a46a7..041e32a 100644 --- a/packages/react/src/pages/home.tsx +++ b/packages/react/src/pages/home.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { getInstance } from "sdk"; import styles from "./home.module.css"; import mxLogo from "/mx.svg"; @@ -6,22 +6,57 @@ 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(""); + 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 (
@@ -41,18 +76,35 @@ export function HomePage() {

A/B Testing on React

Experiments

- {experiments.map((experiment) => ( -
{JSON.stringify(experiment, null, 2)}
- ))} + {JSON.stringify(Object.values(experiments))}

Subject Attributes

{JSON.stringify(attributes, null, 2)} +
+ setId(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleUpdateId()} + /> + +

Assignment

{JSON.stringify(assignment, null, 2)}
+
+

Forced Variations

+ {JSON.stringify(forcedVariations, null, 2)} +
+ + + +
+
); } diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index eb0c409..984de8a 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -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 === "") { @@ -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(":"); }