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>
);
}
PropTypeDescription
optionsRevTurbineInitOptionsSDK initialization options (see Configuration Reference)
bootstrapPlacementsBootstrapPlacementInput[]Placements to preload on mount
childrenReactNodeYour 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:

FieldTypeDescription
sdkRevTurbineCustomerSdk | nullSDK instance (null until ready)
isReadybooleanWhether the SDK has initialized
errorstringError message if initialization failed
setContext(ctx: RevTurbineUserContext) => voidUpdate 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:

FieldTypeDefaultDescription
surfaceSlotRevTurbineSurfaceSlotConfigSlot configuration (id, surfaceTemplateIds)
userIdstringOverride user ID
contextMode'auto' | 'override''auto'Context resolution mode
traitsRecord<string, string | number | boolean>Additional targeting traits
ttlMsnumber300000Cache TTL in milliseconds
autoLoadbooleantrueLoad placement on mount

Returns:

FieldTypeDescription
isLoadingbooleanWhether the placement is being resolved
errorstringError message if resolution failed
visiblebooleanWhether the placement should be shown
decisionRevTurbinePlacementDecision | nullFull decision object
contentobject | nullResolved 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:

FieldTypeDefaultDescription
handlestringEntitlement handle (required)
autoCheckbooleantrueCheck on mount
autoGatebooleanfalseAuto-resolve gated placement if denied
gatePlacementRequestobjectPlacement config for the gate

Returns:

FieldTypeDescription
isLoadingbooleanWhether the check is in progress
errorstring | nullError message
resultEntitlementResult | nullFull result
allowedbooleanAccess granted
limitedbooleanApproaching limit
deniedbooleanAccess denied
gatedPlacementPlacementOutput | nullUpgrade 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 <Slot> — it resolves the placement and renders the matching built-in slot:

import { Slot } from '@revturbine/sdk';
// Banner
<Slot id="hero_banner" surfaceType="banner" />
// Modal gate
<Slot id="export_gate" surfaceType="modal" />
// Inline card
<Slot 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.