@variantlab/react 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +236 -70
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -13,50 +13,92 @@ npm install @variantlab/core@alpha @variantlab/react@alpha
13
13
 
14
14
  **Peer dependencies:** `react ^18.2.0 || ^19.0.0`
15
15
 
16
- ## Quick start
16
+ ---
17
+
18
+ ## Complete example
19
+
20
+ ### `experiments.json`
21
+
22
+ ```json
23
+ {
24
+ "version": 1,
25
+ "experiments": [
26
+ {
27
+ "id": "hero-layout",
28
+ "name": "Hero section layout",
29
+ "type": "render",
30
+ "default": "centered",
31
+ "variants": [
32
+ { "id": "centered" },
33
+ { "id": "split" }
34
+ ]
35
+ },
36
+ {
37
+ "id": "cta-copy",
38
+ "name": "CTA button text",
39
+ "type": "value",
40
+ "default": "buy-now",
41
+ "variants": [
42
+ { "id": "buy-now", "value": "Buy now" },
43
+ { "id": "get-started", "value": "Get started" },
44
+ { "id": "try-free", "value": "Try it free" }
45
+ ]
46
+ },
47
+ {
48
+ "id": "pricing",
49
+ "name": "Pricing tier",
50
+ "type": "value",
51
+ "default": "low",
52
+ "assignment": { "strategy": "sticky-hash" },
53
+ "variants": [
54
+ { "id": "low", "value": 9.99, "weight": 50 },
55
+ { "id": "high", "value": 14.99, "weight": 50 }
56
+ ]
57
+ }
58
+ ]
59
+ }
60
+ ```
17
61
 
18
- ### 1. Create the engine and wrap your app
62
+ ### `App.tsx`
19
63
 
20
64
  ```tsx
21
65
  import { createEngine } from "@variantlab/core";
22
66
  import { VariantLabProvider } from "@variantlab/react";
23
67
  import experiments from "./experiments.json";
24
68
 
25
- const engine = createEngine(experiments);
69
+ const engine = createEngine(experiments, {
70
+ context: {
71
+ userId: "user-123",
72
+ platform: "web",
73
+ locale: "en",
74
+ },
75
+ });
26
76
 
27
77
  export default function App() {
28
78
  return (
29
79
  <VariantLabProvider engine={engine}>
30
- <YourApp />
80
+ <HomePage />
31
81
  </VariantLabProvider>
32
82
  );
33
83
  }
34
84
  ```
35
85
 
36
- ### 2. Use hooks
86
+ ### `HomePage.tsx`
37
87
 
38
88
  ```tsx
39
- import { useVariant, useVariantValue } from "@variantlab/react";
40
-
41
- function HeroSection() {
42
- // Get the assigned variant ID
43
- const variant = useVariant("hero-layout"); // "centered" | "split"
44
-
45
- return variant === "split" ? <SplitHero /> : <CenteredHero />;
46
- }
89
+ import { useVariant, useVariantValue, Variant, VariantErrorBoundary } from "@variantlab/react";
47
90
 
48
- function CheckoutButton() {
49
- // Get a value experiment's value directly
50
- const copy = useVariantValue<string>("cta-copy"); // "Buy now" | "Get started"
51
-
52
- return <button>{copy}</button>;
91
+ function HomePage() {
92
+ return (
93
+ <main>
94
+ <VariantErrorBoundary experimentId="hero-layout" fallback={<p>Error loading hero</p>}>
95
+ <HeroSection />
96
+ </VariantErrorBoundary>
97
+ <CheckoutButton />
98
+ <PricingDisplay />
99
+ </main>
100
+ );
53
101
  }
54
- ```
55
-
56
- ### 3. Or use components
57
-
58
- ```tsx
59
- import { Variant, VariantValue } from "@variantlab/react";
60
102
 
61
103
  function HeroSection() {
62
104
  return (
@@ -70,105 +112,229 @@ function HeroSection() {
70
112
  }
71
113
 
72
114
  function CheckoutButton() {
73
- return (
74
- <VariantValue experimentId="cta-copy">
75
- {(value) => <button>{value}</button>}
76
- </VariantValue>
77
- );
115
+ const copy = useVariantValue<string>("cta-copy");
116
+ return <button>{copy}</button>;
117
+ }
118
+
119
+ function PricingDisplay() {
120
+ const price = useVariantValue<number>("pricing");
121
+ return <span>${price}/month</span>;
78
122
  }
79
123
  ```
80
124
 
81
- ## Hooks API
125
+ ---
126
+
127
+ ## Hooks
82
128
 
83
- ### `useVariant(experimentId)`
129
+ ### `useVariant(experimentId)` — get the active variant ID
84
130
 
85
- Returns the assigned variant ID for a render experiment.
131
+ Use this for **render experiments** where you switch between different components or layouts.
86
132
 
87
133
  ```tsx
88
- const variantId = useVariant("hero-layout"); // "centered" | "split"
134
+ import { useVariant } from "@variantlab/react";
135
+
136
+ function HeroSection() {
137
+ const layout = useVariant("hero-layout");
138
+ // Returns: "centered" | "split"
139
+
140
+ if (layout === "split") {
141
+ return <SplitHero />;
142
+ }
143
+ return <CenteredHero />;
144
+ }
89
145
  ```
90
146
 
91
- ### `useVariantValue<T>(experimentId)`
147
+ ### `useVariantValue<T>(experimentId)` — get the experiment value
92
148
 
93
- Returns the value of a value experiment.
149
+ Use this for **value experiments** where variants carry data (strings, numbers, booleans, objects).
94
150
 
95
151
  ```tsx
96
- const copy = useVariantValue<string>("cta-copy"); // "Buy now"
97
- const price = useVariantValue<number>("pricing"); // 9.99
152
+ import { useVariantValue } from "@variantlab/react";
153
+
154
+ function CheckoutButton() {
155
+ const buttonText = useVariantValue<string>("cta-copy");
156
+ // Returns: "Buy now" | "Get started" | "Try it free"
157
+ return <button>{buttonText}</button>;
158
+ }
159
+
160
+ function PricingDisplay() {
161
+ const price = useVariantValue<number>("pricing");
162
+ // Returns: 9.99 | 14.99
163
+ return <span>${price}/month</span>;
164
+ }
98
165
  ```
99
166
 
100
- ### `useExperiment(experimentId)`
167
+ ### `useExperiment(experimentId)` — get full experiment state
101
168
 
102
- Returns the full experiment state including metadata.
169
+ Returns the variant ID, experiment config, and whether it's been manually overridden. Useful for debug UIs or analytics.
103
170
 
104
171
  ```tsx
105
- const { variantId, experiment, isOverridden } = useExperiment("hero-layout");
172
+ import { useExperiment } from "@variantlab/react";
173
+
174
+ function ExperimentInfo() {
175
+ const { variantId, experiment, isOverridden } = useExperiment("hero-layout");
176
+
177
+ return (
178
+ <div>
179
+ <p>Experiment: {experiment.name}</p>
180
+ <p>Current variant: {variantId}</p>
181
+ {isOverridden && <p style={{ color: "orange" }}>⚠ Manually overridden</p>}
182
+ </div>
183
+ );
184
+ }
106
185
  ```
107
186
 
108
- ### `useSetVariant()`
187
+ ### `useSetVariant()` — override a variant
109
188
 
110
- Returns a function to override variant assignments (useful for debug UIs).
189
+ Returns a function to force-assign a variant. Useful for building debug UIs, admin panels, or testing during development.
111
190
 
112
191
  ```tsx
113
- const setVariant = useSetVariant();
114
- setVariant("hero-layout", "split"); // force split variant
192
+ import { useSetVariant, useVariant } from "@variantlab/react";
193
+
194
+ function VariantPicker() {
195
+ const setVariant = useSetVariant();
196
+ const current = useVariant("hero-layout");
197
+
198
+ return (
199
+ <div>
200
+ <p>Current: {current}</p>
201
+ <button onClick={() => setVariant("hero-layout", "centered")}>Centered</button>
202
+ <button onClick={() => setVariant("hero-layout", "split")}>Split</button>
203
+ </div>
204
+ );
205
+ }
115
206
  ```
116
207
 
117
- ### `useVariantLabEngine()`
208
+ ### `useVariantLabEngine()` — access the engine directly
118
209
 
119
- Returns the engine instance directly.
210
+ Returns the raw engine instance for advanced operations like resetting all overrides, updating context, or subscribing to changes.
120
211
 
121
212
  ```tsx
122
- const engine = useVariantLabEngine();
123
- engine.resetAll();
213
+ import { useVariantLabEngine } from "@variantlab/react";
214
+
215
+ function SettingsPanel() {
216
+ const engine = useVariantLabEngine();
217
+
218
+ return (
219
+ <div>
220
+ <button onClick={() => engine.resetAll()}>Reset all experiments</button>
221
+ <button onClick={() => engine.updateContext({ locale: "bn" })}>
222
+ Switch to Bengali
223
+ </button>
224
+ </div>
225
+ );
226
+ }
124
227
  ```
125
228
 
126
- ### `useRouteExperiments()`
229
+ ### `useRouteExperiments()` — get experiments targeting the current route
127
230
 
128
- Returns experiments that target the current route.
231
+ Returns only experiments whose targeting rules match the current URL path. Useful for showing relevant experiments in a debug panel.
129
232
 
130
233
  ```tsx
131
- const experiments = useRouteExperiments();
234
+ import { useRouteExperiments } from "@variantlab/react";
235
+
236
+ function RouteDebugPanel() {
237
+ const experiments = useRouteExperiments();
238
+
239
+ return (
240
+ <ul>
241
+ {experiments.map((exp) => (
242
+ <li key={exp.id}>{exp.name}: {exp.variantId}</li>
243
+ ))}
244
+ </ul>
245
+ );
246
+ }
132
247
  ```
133
248
 
249
+ ---
250
+
134
251
  ## Components
135
252
 
136
- ### `<Variant>`
253
+ ### `<Variant>` — render-swap by variant ID
254
+
255
+ Renders the child matching the active variant. Cleaner than if/switch when you have distinct JSX per variant.
256
+
257
+ ```tsx
258
+ import { Variant } from "@variantlab/react";
259
+
260
+ function OnboardingPage() {
261
+ return (
262
+ <Variant experimentId="onboarding-flow" fallback={<ClassicOnboarding />}>
263
+ {{
264
+ classic: <ClassicOnboarding />,
265
+ "quick-start": <QuickStartOnboarding />,
266
+ guided: <GuidedOnboarding />,
267
+ }}
268
+ </Variant>
269
+ );
270
+ }
271
+ ```
272
+
273
+ ### `<VariantValue>` — render-prop for value experiments
137
274
 
138
- Renders the matching variant child by ID.
275
+ Passes the experiment value to a render function. Useful when you want to keep the value inline.
139
276
 
140
277
  ```tsx
141
- <Variant experimentId="hero-layout" fallback={<Default />}>
142
- {{
143
- centered: <CenteredHero />,
144
- split: <SplitHero />,
145
- }}
146
- </Variant>
278
+ import { VariantValue } from "@variantlab/react";
279
+
280
+ function WelcomeBanner() {
281
+ return (
282
+ <VariantValue experimentId="cta-copy">
283
+ {(value) => <h2>{value}</h2>}
284
+ </VariantValue>
285
+ );
286
+ }
147
287
  ```
148
288
 
149
- ### `<VariantValue>`
289
+ ### `<VariantErrorBoundary>` — crash-safe experiments
150
290
 
151
- Render-prop component for value experiments.
291
+ Wraps an experiment in an error boundary. If a variant crashes N times within a time window, the engine automatically rolls back to the default variant.
152
292
 
153
293
  ```tsx
154
- <VariantValue experimentId="cta-copy">
155
- {(value) => <span>{value}</span>}
156
- </VariantValue>
294
+ import { VariantErrorBoundary } from "@variantlab/react";
295
+
296
+ function SafeHeroSection() {
297
+ return (
298
+ <VariantErrorBoundary
299
+ experimentId="hero-layout"
300
+ fallback={<p>Something went wrong. Showing default layout.</p>}
301
+ >
302
+ <HeroSection />
303
+ </VariantErrorBoundary>
304
+ );
305
+ }
157
306
  ```
158
307
 
159
- ### `<VariantErrorBoundary>`
308
+ ### `<VariantLabProvider>` — context provider
160
309
 
161
- Error boundary with crash-rollback integration. If a variant crashes N times, the engine auto-reverts to the default variant.
310
+ Wraps your app and provides the engine to all hooks and components. Must be near the top of your component tree.
162
311
 
163
312
  ```tsx
164
- <VariantErrorBoundary experimentId="hero-layout" fallback={<ErrorFallback />}>
165
- <HeroSection />
166
- </VariantErrorBoundary>
313
+ import { VariantLabProvider } from "@variantlab/react";
314
+
315
+ export default function App() {
316
+ return (
317
+ <VariantLabProvider engine={engine}>
318
+ {/* All useVariant/useVariantValue/etc. hooks work inside here */}
319
+ <Router />
320
+ </VariantLabProvider>
321
+ );
322
+ }
167
323
  ```
168
324
 
325
+ ---
326
+
169
327
  ## Type safety with codegen
170
328
 
171
- Run `npx variantlab generate` to generate TypeScript types from your `experiments.json`. Experiment IDs and variant IDs become literal types — typos become compile errors.
329
+ Generate TypeScript types from your config so typos become compile errors:
330
+
331
+ ```bash
332
+ npx @variantlab/cli@alpha generate
333
+ ```
334
+
335
+ After running, `useVariant("hero-layout")` returns `"centered" | "split"` as a literal union type. Passing a non-existent experiment ID like `useVariant("typo")` is a compile error.
336
+
337
+ ---
172
338
 
173
339
  ## License
174
340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@variantlab/react",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "React hooks and components for variantlab.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,18 +40,18 @@
40
40
  ],
41
41
  "repository": {
42
42
  "type": "git",
43
- "url": "git+https://github.com/variantlab/variantlab.git",
43
+ "url": "git+https://github.com/Minhaj-Rabby/variantlab.git",
44
44
  "directory": "packages/react"
45
45
  },
46
46
  "bugs": {
47
- "url": "https://github.com/variantlab/variantlab/issues"
47
+ "url": "https://github.com/Minhaj-Rabby/variantlab/issues"
48
48
  },
49
- "homepage": "https://github.com/variantlab/variantlab#readme",
49
+ "homepage": "https://github.com/Minhaj-Rabby/variantlab#readme",
50
50
  "engines": {
51
51
  "node": ">=18.17"
52
52
  },
53
53
  "dependencies": {
54
- "@variantlab/core": "0.1.1"
54
+ "@variantlab/core": "0.1.3"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": "^18.2.0 || ^19.0.0"