@variantlabs/react
v0.2.0-alpha.2
Published
VariantLabs React bindings: <VariantLabsProvider>, useVariant, useOutcome, <Variant>
Readme
@variantlabs/react
React bindings for VariantLabs — a provider, hooks, and a render-prop component for feature flags, experiments, and AI config.
Install
npm install @variantlabs/reactReact 18 or 19 is a peer dependency. @variantlabs/js comes along as a dependency — you don't need to install it separately.
Quickstart
Build the client, pass it to the provider:
import { initVariantLabs, VariantLabsProvider } from "@variantlabs/react"
const vl = initVariantLabs({
apiKey: import.meta.env.VITE_VARIANTLABS_KEY,
appKey: "web",
environmentKey: "production",
})
export function App() {
return (
<VariantLabsProvider client={vl}>
<Checkout />
</VariantLabsProvider>
)
}Then read variants anywhere below it:
import { useVariant } from "@variantlabs/react"
function Checkout() {
const { value, isLoading } = useVariant("checkout-button-color", undefined, "blue")
if (isLoading) return <Skeleton />
return <button style={{ background: value as string }}>Buy now</button>
}Create the client outside your component tree so it isn't recreated on every render.
useVariant
const { value, variantKey, isLoading, assignment } = useVariant(configKey, ctx?, fallback?)Once the provider is ready, this resolves synchronously from the local cache — no loading flash for returning visitors. Before that it awaits the client, returning fallback in the meantime. If evaluation fails for any reason it returns fallback rather than throwing.
// With targeting context
const { value } = useVariant("pricing-tier", { subjectKey: user.id, attributes: { plan: user.plan } })
// Typed
const { value } = useVariant<{ headline: string }>("hero-copy", undefined, { headline: "Welcome" })The ctx object is compared by stable hash, so an inline literal won't cause a re-evaluation loop.
useOutcome
Returns a stable tracker function — safe in dependency arrays.
import { useOutcome, useVariant } from "@variantlabs/react"
function BuyButton() {
const { assignment } = useVariant("checkout-flow")
const track = useOutcome()
return (
<button
onClick={() => {
track({
assignmentId: assignment!.assignmentId,
outcomeKey: "purchase",
success: true,
numericValue: 49.99,
})
}}
>
Buy now
</button>
)
}<Variant>
A render-prop component, for when a hook would mean splitting out a component just to call it.
import { Variant } from "@variantlabs/react"
<Variant
configKey="homepage-hero"
fallback={<DefaultHero />}
render={(assignment, value) => <Hero {...(value as HeroProps)} />}
/>fallback renders while loading and if evaluation fails.
useVariantLabsClient
Escape hatch to the underlying @variantlabs/js client — for imperative calls like flush() or evaluating outside of render.
const client = useVariantLabsClient()
await client?.flush()Fails open, always
If client.init() rejects — network down, bad key, API unreachable — the provider still flips to ready. Hooks then render their fallbacks instead of suspending forever or throwing. A VariantLabs outage degrades to your default experience; it never takes down your app.
Next.js and RSC
The build prepends "use client", so these components work in the App Router without extra wrapping.
For server-side evaluation — baking a variant into SSR HTML with no client flash — use @variantlabs/node in a server component and pass the result down as props. The Next.js example shows both halves working together.
API
| | |
| --- | --- |
| <VariantLabsProvider client> | Context provider; calls init() |
| useVariant(key, ctx?, fallback?) | { value, variantKey, isLoading, assignment } |
| useOutcome() | Stable (input) => void tracker |
| useVariantLabsClient() | The underlying client, or null |
| <Variant configKey ctx? fallback? render> | Render-prop component |
| VariantLabsContext | Raw context, for custom hooks |
initVariantLabs and the core types (AssignmentResult, EvaluationContext, TrackOutcomeInput, VariantValue, …) are re-exported, so you only import from this package.
Compatibility
React 18 / 19. Node >= 22 for tooling. Ships ESM + CJS + type declarations.
License
Apache-2.0
