React Adapter
The @presage-kit/react package provides React components and hooks for building adaptive interfaces.
Installation
Section titled “Installation”pnpm add @presage-kit/core @presage-kit/reactAdaptiveProvider
Section titled “AdaptiveProvider”Provides the AdaptiveClient to the entire component tree via React context.
import { AdaptiveProvider } from '@presage-kit/react'import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
const client = createAdaptiveClient({ rules: [/* ... */], persistence: { driver: createLocalStorageDriver('my-app') },})
function App() { return ( <AdaptiveProvider client={client}> <YourApp /> </AdaptiveProvider> )}Props:
| Prop | Type | Description |
|---|---|---|
client | AdaptiveClient | The client instance from createAdaptiveClient() |
children | ReactNode | Your application tree |
<Adaptive> Component
Section titled “<Adaptive> Component”Declares an adaptation point and renders the matching variant.
import { Adaptive, Variant } from '@presage-kit/react'
function Dashboard() { return ( <Adaptive id="dashboard-layout" defaultVariant="standard"> <Variant id="compact"> <CompactDashboard /> </Variant> <Variant id="standard"> <StandardDashboard /> </Variant> <Variant id="advanced"> <AdvancedDashboard /> </Variant> </Adaptive> )}<Adaptive> props:
| Prop | Type | Description |
|---|---|---|
id | string | Unique adaptation point identifier |
defaultVariant | string | Variant to show when no rule matches |
children | ReactNode | Must contain <Variant> components |
<Variant> props:
| Prop | Type | Description |
|---|---|---|
id | string | Variant identifier (referenced in rule actions) |
children | ReactNode | Content to render when this variant is selected |
The <Adaptive> component internally uses useAdaptive() and automatically tracks impressions when the selected variant changes.
<AdaptiveNav> Component
Section titled “<AdaptiveNav> Component”Renders a list of navigation items that can be reordered by adaptation rules.
import { AdaptiveNav, type NavItem } from '@presage-kit/react'
interface MenuItem extends NavItem { label: string href: string icon: string}
const menuItems: MenuItem[] = [ { id: 'home', label: 'Home', href: '/', icon: 'home' }, { id: 'analytics', label: 'Analytics', href: '/analytics', icon: 'chart' }, { id: 'settings', label: 'Settings', href: '/settings', icon: 'gear' },]
function Sidebar() { return ( <AdaptiveNav id="sidebar-nav" items={menuItems} renderItem={(item, index) => ( <a key={item.id} href={item.href}> {item.icon} {item.label} </a> )} /> )}With a reorder rule, the navigation order changes based on user context:
{ id: 'power-user-nav-order', adaptationId: 'sidebar-nav', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['analytics', 'home', 'settings'] },}With a hide rule, the entire navigation is hidden:
{ id: 'hide-nav-for-new-users', adaptationId: 'sidebar-nav', priority: 20, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'hide' },}Props:
| Prop | Type | Description |
|---|---|---|
id | string | Adaptation point identifier |
items | T[] (extends NavItem) | Array of items, each with at least an id field |
renderItem | (item: T, index: number) => ReactNode | Render function for each item |
NavItem interface:
interface NavItem { id: string [key: string]: unknown}useAdaptive() Hook
Section titled “useAdaptive() Hook”The primary hook for resolving adaptation points. Returns the selected variant, full resolution details, context, and a scoped track function.
import { useAdaptive } from '@presage-kit/react'
function FeatureDiscovery() { const { selectedVariant, resolution, context, track } = useAdaptive('feature-discovery', { variants: ['tooltip', 'banner', 'modal'] as const, defaultVariant: 'tooltip', })
// Track a conversion event scoped to this adaptation point function handleDismiss() { track('feature_discovery_dismissed') }
switch (selectedVariant) { case 'tooltip': return <Tooltip onDismiss={handleDismiss} /> case 'banner': return <Banner onDismiss={handleDismiss} /> case 'modal': return <Modal onDismiss={handleDismiss} /> }}Parameters:
| Param | Type | Description |
|---|---|---|
adaptationId | string | Unique identifier for the adaptation point |
config.variants | readonly string[] | Available variant IDs |
config.defaultVariant | string | Fallback variant |
config.strategy | Strategy | Optional. Defaults to { type: 'rules' } |
Return type:
| Field | Type | Description |
|---|---|---|
selectedVariant | V | The selected variant (type-narrowed to your variant union) |
resolution | ResolvedAdaptation | Full resolution details (strategy, reason, timestamp) |
context | UserContext | Current user context |
track | (event: string, properties?: Record<string, unknown>) => void | Track function scoped to this adaptation point |
TypeScript generics: The hook infers the variant type from variants:
// selectedVariant is typed as 'tooltip' | 'banner' | 'modal'const { selectedVariant } = useAdaptive('feature-discovery', { variants: ['tooltip', 'banner', 'modal'] as const, defaultVariant: 'tooltip',})Automatic impression tracking: When the selected variant changes, useAdaptive() automatically tracks an presage:impression event with the adaptation ID, variant, and strategy.
useVariant() Hook
Section titled “useVariant() Hook”A lighter hook that returns only the selected variant string. Use this when you don’t need the full resolution details.
import { useVariant } from '@presage-kit/react'
function CTAButton() { const variant = useVariant('cta-style', { variants: ['primary', 'secondary', 'subtle'] as const, defaultVariant: 'primary', })
return <button className={`cta cta--${variant}`}>Get Started</button>}Parameters: Same as useAdaptive().
Return type: V — The selected variant string.
useAdaptiveClient() Hook
Section titled “useAdaptiveClient() Hook”Returns the AdaptiveClient instance from context. Useful for imperative operations like tracking and identifying.
import { useAdaptiveClient } from '@presage-kit/react'
function TrackingButton() { const client = useAdaptiveClient()
function handleClick() { client.track('feature_used', { featureId: 'export' }) }
return <button onClick={handleClick}>Export</button>}Return type: AdaptiveClient
Throws an error if called outside of <AdaptiveProvider>.
Full Example: SaaS Dashboard
Section titled “Full Example: SaaS Dashboard”This example shows an adaptive sidebar and onboarding flow for a SaaS dashboard.
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
export const client = createAdaptiveClient({ rules: [ // Sidebar: prioritize analytics for power users { id: 'power-user-sidebar', adaptationId: 'sidebar', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['analytics', 'dashboards', 'settings', 'home'] }, }, // Onboarding: guided tour for new users { id: 'new-user-tour', adaptationId: 'onboarding', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'show', variantId: 'guided-tour' }, }, // Onboarding: hide for experienced users { id: 'experienced-no-onboarding', adaptationId: 'onboarding', priority: 5, conditions: { any: [ { field: 'maturity', operator: 'eq', value: 'active' }, { field: 'maturity', operator: 'eq', value: 'power' }, ], }, action: { type: 'hide' }, }, ], persistence: { driver: createLocalStorageDriver('saas-demo'), },})import { AdaptiveProvider } from '@presage-kit/react'import { client } from './lib/adaptive-client'import { Sidebar } from './components/Sidebar'import { Onboarding } from './components/Onboarding'import { Dashboard } from './components/Dashboard'
export function App() { return ( <AdaptiveProvider client={client}> <div style={{ display: 'flex' }}> <Sidebar /> <main> <Onboarding /> <Dashboard /> </main> </div> </AdaptiveProvider> )}import { AdaptiveNav, type NavItem } from '@presage-kit/react'
interface SidebarItem extends NavItem { label: string href: string}
const items: SidebarItem[] = [ { id: 'home', label: 'Home', href: '/' }, { id: 'dashboards', label: 'Dashboards', href: '/dashboards' }, { id: 'analytics', label: 'Analytics', href: '/analytics' }, { id: 'settings', label: 'Settings', href: '/settings' },]
export function Sidebar() { return ( <nav> <AdaptiveNav id="sidebar" items={items} renderItem={(item) => ( <a key={item.id} href={item.href}> {item.label} </a> )} /> </nav> )}import { Adaptive, Variant } from '@presage-kit/react'
export function Onboarding() { return ( <Adaptive id="onboarding" defaultVariant="standard"> <Variant id="guided-tour"> <div> <h2>Welcome! Let us show you around.</h2> <p>Follow the steps to get set up.</p> </div> </Variant> <Variant id="standard"> <div> <h2>What is new</h2> <p>Check out the latest features.</p> </div> </Variant> </Adaptive> )}