Skip to content

React Integration

The React integration provides a provider, hooks, and drop-in components for placement rendering and entitlement checks.

Wrap your app (or a subtree) with the provider:

import { RevTurbineProvider } from '@revturbine/sdk';
import exportedConfig from './exported_config.json';
import { useMemo } from 'react';
export default function App() {
const options = useMemo(() => ({
localRuntime: { exportedConfig },
user: {
id: 'user_123',
context: { plan_handle: 'starter' },
},
}), []);
return (
<RevTurbineProvider options={options}>
<Dashboard />
</RevTurbineProvider>
);
}

| Prop | Type | Description | |---|---|---| | options | RevTurbineInitOptions | SDK initialization options (see Configuration Reference) | | bootstrapPlacements | BootstrapPlacementInput[] | Placements to preload on mount | | children | ReactNode | Your app |

Preload placement decisions during initialization to eliminate loading flicker:

<RevTurbineProvider
options={options}
bootstrapPlacements={[
{ placement: { name: 'hero_banner' }, userId: 'user_123' },
{ placement: { name: 'nav_cta' }, userId: 'user_123' },
]}
>

Access the SDK instance and provider state:

import { useRevTurbine } from '@revturbine/sdk';
function StatusBar() {
const { sdk, isReady, error, setContext } = useRevTurbine();
if (!isReady) return <span>Loading SDK…</span>;
if (error) return <span>SDK error: {error}</span>;
return <span>SDK ready</span>;
}

Returns:

| Field | Type | Description | |---|---|---| | sdk | RevTurbineCustomerSdk \| null | SDK instance (null until ready) | | isReady | boolean | Whether the SDK has initialized | | error | string | Error message if initialization failed | | setContext | (ctx: RevTurbineUserContext) => void | Update user context dynamically |

Resolve a placement for a surface slot:

import { usePlacement } from '@revturbine/sdk';
function UpgradeBanner() {
const {
visible,
content,
isLoading,
dismiss,
ctaClick,
} = usePlacement({
surfaceSlot: { id: 'dashboard_banner', surfaceTemplateIds: ['banner_placement'] },
});
if (!visible) return null;
return (
<div>
<h3>{content?.header}</h3>
<p>{content?.body}</p>
<button onClick={() => ctaClick()}>{content?.cta_label}</button>
<button onClick={() => dismiss()}></button>
</div>
);
}

Options:

| Field | Type | Default | Description | |---|---|---|---| | surfaceSlot | RevTurbineSurfaceSlotConfig | — | Slot configuration (id, surfaceTemplateIds) | | userId | string | — | Override user ID | | contextMode | 'auto' \| 'override' | 'auto' | Context resolution mode | | traits | Record<string, string \| number \| boolean> | — | Additional targeting traits | | ttlMs | number | 300000 | Cache TTL in milliseconds | | autoLoad | boolean | true | Load placement on mount |

Returns:

| Field | Type | Description | |---|---|---| | isLoading | boolean | Whether the placement is being resolved | | error | string | Error message if resolution failed | | visible | boolean | Whether the placement should be shown | | decision | RevTurbinePlacementDecision \| null | Full decision object | | content | object \| null | Resolved content (header, body, cta_label, etc.) | | refresh | () => Promise<void> | Re-resolve the placement | | dismiss | (cooldownMs?) => Promise<void> | Dismiss and suppress | | snooze | (seconds?) => Promise<void> | Temporarily suppress | | remindMeLater | (seconds?) => Promise<void> | Alias for snooze | | ctaClick | (target?) => Promise<void> | Record CTA click | | ctaComplete | (target?) => Promise<void> | Record CTA completion |

Check whether a user has access to a feature:

import { useEntitlement } from '@revturbine/sdk';
function ExportButton() {
const { allowed, denied, gatedPlacement, recheck } = useEntitlement({
handle: 'data_export',
autoGate: true, // auto-resolve upgrade placement if denied
});
if (denied && gatedPlacement) {
return <UpgradePrompt placement={gatedPlacement} />;
}
return <button disabled={!allowed}>Export Video</button>;
}

Options:

| Field | Type | Default | Description | |---|---|---|---| | handle | string | — | Entitlement handle (required) | | autoCheck | boolean | true | Check on mount | | autoGate | boolean | false | Auto-resolve gated placement if denied | | gatePlacementRequest | object | — | Placement config for the gate |

Returns:

| Field | Type | Description | |---|---|---| | isLoading | boolean | Whether the check is in progress | | error | string \| null | Error message | | result | EntitlementResult \| null | Full result | | allowed | boolean | Access granted | | limited | boolean | Approaching limit | | denied | boolean | Access denied | | gatedPlacement | PlacementOutput \| null | Upgrade placement (if autoGate: true) | | recheck | () => Promise<void> | Re-check entitlement |

Get current usage balances:

import { useUsageSnapshot } from '@revturbine/sdk';
function UsagePanel() {
const { usage, refresh } = useUsageSnapshot();
return (
<div>
<p>API calls: {usage.api_calls?.current} / {usage.api_calls?.limit}</p>
<button onClick={refresh}>Refresh</button>
</div>
);
}

Access the resolved theme:

import { useRevTurbineTheme } from '@revturbine/sdk';
function ThemedCard({ children }) {
const theme = useRevTurbineTheme();
return (
<div style={{
background: theme.colors.surface,
borderRadius: theme.shape.borderRadius,
color: theme.colors.text,
}}>
{children}
</div>
);
}

For the simplest integration, use SurfaceSlotComponent — it resolves the placement and renders the matching built-in slot:

import { SurfaceSlotComponent } from '@revturbine/sdk';
// Banner
<SurfaceSlotComponent id="hero_banner" surfaceType="banner" />
// Modal gate
<SurfaceSlotComponent id="export_gate" surfaceType="modal" />
// Inline card
<SurfaceSlotComponent id="pricing_card" surfaceType="in_page" />

See the Component Gallery for interactive demos of every built-in slot component.

Update the user context after login, plan change, or navigation:

const { setContext } = useRevTurbine();
// After login
setContext({
id: user.id,
plan: { id: user.planId },
traits: { role: user.role },
});

All active placements and entitlement checks re-evaluate automatically when context changes.