React
React components and hooks for serving optimized variants with Levered.
The React integration provides a provider component and a hook that handle variant fetching, caching, loading states, and exposure tracking. Import everything from the react submodule:
import { LeveredProvider, useVariant } from '@levered_dev/sdk/react';LeveredProvider
Wrap your application (or the subtree that needs variants) with LeveredProvider. It creates a shared LeveredClient instance and makes it available to all useVariant calls below it.
import { LeveredProvider } from '@levered_dev/sdk/react';
function App() {
return (
<LeveredProvider
apiUrl="https://api.levered.dev"
anonymousId={getOrCreateSessionId()}
onExposure={(event) => {
// Log to your warehouse (BigQuery, Snowflake, Segment, etc.)
analytics.track('levered_exposure', event);
}}
>
<YourApp />
</LeveredProvider>
);
}Props
| Prop | Type | Required | Description |
|---|---|---|---|
apiUrl | string | Yes | Base URL of the Levered API (e.g. https://api.levered.dev). |
anonymousId | string | Yes | Stable anonymous identifier for the current user or session. Must match the anonymous_id column in your warehouse exposure table. |
onExposure | (event: ExposureEvent) => void | No | Called every time the SDK exposes a user to a variant. Use this to log exposure events to your warehouse. |
The anonymousId should be a persistent identifier that you generate and store (e.g. in a cookie or local storage). It ties the variant assignment to the user so Levered can learn which variants perform best.
useVariant
Fetch the best variant for a given optimization. The hook returns the variant values immediately (using the fallback while loading) so your component always has something to render.
import { useVariant } from '@levered_dev/sdk/react';
function HeroSection() {
const { variant, isLoading } = useVariant({
optimizationId: 'abc-123',
fallback: { headline: 'Welcome', cta_text: 'Get Started' },
context: { device: 'mobile' },
});
return (
<section>
<h1>{variant.headline}</h1>
<button>{variant.cta_text}</button>
</section>
);
}Options
| Option | Type | Required | Description |
|---|---|---|---|
optimizationId | string | Yes | UUID of the optimization to serve variants from. |
fallback | T | Yes | Default variant values shown while loading and on error. Also defines the TypeScript shape of the variant object. |
context | Record<string, unknown> | No | Context attributes for contextual bandits (CMAB). For example: { device: 'mobile', country: 'US' }. |
Return value
| Field | Type | Description |
|---|---|---|
variant | T | The resolved variant values. Equals fallback while loading or if the request fails. |
isLoading | boolean | true while the initial fetch is in flight. |
Type safety
The fallback prop defines the shape of the variant object. TypeScript will infer the type from your fallback, so variant.headline and variant.cta_text are strongly typed.
// TypeScript knows variant has { headline: string; cta_text: string }
const { variant } = useVariant({
optimizationId: 'abc-123',
fallback: { headline: 'Welcome', cta_text: 'Get Started' },
});Re-fetching
The hook automatically re-fetches when optimizationId, anonymousId (from the provider), or context changes. You do not need to manage refetching yourself.
ExposureEvent
When the SDK assigns a variant to a user, it fires the onExposure callback with an event that has this shape:
interface ExposureEvent {
/** The user's anonymous identifier. */
anonymousId: string;
/** UUID of the optimization that served this variant. */
optimizationId: string;
/** The design-factor values of the assigned variant. */
variant: Record<string, string | number | boolean>;
/** Context attributes sent with the request. */
context: Record<string, unknown>;
/** ISO-8601 timestamp of the exposure. */
timestamp: string;
/** Which bandit model produced this routing: 'mab', 'cmab', or 'holdout'. */
modelType: 'mab' | 'cmab' | 'holdout';
/** Whether the model had enough data to route (true) or fell back to random sampling (false). */
ready: boolean;
}You must forward this event to your data warehouse. Levered reads exposure data from your warehouse during model training -- if exposures are not logged, the model cannot learn.
LeveredAdminMenu
A debug overlay for manually overriding variant assignments during development. It renders a docked right-edge drawer labelled "Variant Preview" that lets you pick specific values for each design feature.
import { LeveredAdminMenu } from '@levered_dev/sdk/react';
function App() {
const { variant, isLoading } = useVariant({
optimizationId: 'abc-123',
fallback: { headline: 'Welcome', cta_text: 'Get Started' },
});
const [override, setOverride] = useState<Record<string, string | number | boolean> | null>(null);
const activeVariant = override ?? variant;
return (
<>
<HeroSection variant={activeVariant} />
<LeveredAdminMenu
enabled={process.env.NODE_ENV === 'development'}
variants={[
{
name: 'headline',
label: 'Headline',
options: [
{ value: 'Welcome', label: 'Welcome' },
{ value: 'Hello there', label: 'Hello there' },
{ value: 'Try it free', label: 'Try it free' },
],
},
{
name: 'cta_text',
label: 'CTA Text',
options: [
{ value: 'Get Started', label: 'Get Started' },
{ value: 'Sign Up Now', label: 'Sign Up Now' },
],
},
]}
currentValues={activeVariant}
onOverride={(values) => setOverride(values)}
onReset={() => setOverride(null)}
/>
</>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Whether to render the preview drawer. |
variants | AdminVariantConfig[] | -- | Array of design features with their possible values. |
width | number | 272 | Drawer width in px. |
currentValues | Record<string, string | number | boolean> | -- | The currently active variant values. |
onOverride | (values: Record<string, string | number | boolean>) => void | -- | Called when the user selects a variant override. |
onReset | () => void | -- | Called when the user clicks "Reset to Live". If not provided, the reset button is hidden. |
AdminVariantConfig
Each entry in the variants array describes one design feature:
interface AdminVariantConfig {
/** Property name (e.g. "headline"). */
name: string;
/** Display label in the UI (defaults to name). */
label?: string;
/** Possible values to switch between. */
options: Array<{
value: string | number | boolean;
label?: string;
}>;
}Only enable the admin menu in development or staging environments. It is a debugging tool and should not be visible to end users.
Full example
Putting it all together -- provider, hook, exposure logging, and admin menu:
import { useState } from 'react';
import { LeveredProvider, useVariant, LeveredAdminMenu } from '@levered_dev/sdk/react';
function App() {
return (
<LeveredProvider
apiUrl="https://api.levered.dev"
anonymousId={getOrCreateSessionId()}
onExposure={(event) => {
analytics.track('levered_exposure', event);
}}
>
<HeroPage />
</LeveredProvider>
);
}
function HeroPage() {
const { variant, isLoading } = useVariant({
optimizationId: 'abc-123',
fallback: { headline: 'Welcome', cta_text: 'Get Started' },
});
const [override, setOverride] = useState<Record<string, string | number | boolean> | null>(null);
const active = override ?? variant;
return (
<main>
<h1>{active.headline}</h1>
<button>{active.cta_text}</button>
<LeveredAdminMenu
enabled={process.env.NODE_ENV === 'development'}
variants={[
{
name: 'headline',
options: [
{ value: 'Welcome' },
{ value: 'Hello there' },
{ value: 'Try it free' },
],
},
{
name: 'cta_text',
options: [
{ value: 'Get Started' },
{ value: 'Sign Up Now' },
],
},
]}
currentValues={active}
onOverride={setOverride}
onReset={() => setOverride(null)}
/>
</main>
);
}