Using CADENCE in React
CADENCE does not have a dedicated React package. The standard JavaScript SDK works in any framework, including React, Create React App, Vite, and Next.js.
No @cadence-ab/react package exists
If you've seen references to @cadence-ab/react, those are incorrect. CADENCE provides one SDK — a vanilla JavaScript SDK that works in any framework. This page shows how to use it in React apps.
Setup — CDN script tag
Add the script tag to your HTML. The SDK auto-initializes when the data-project attribute is present and is then available as window.Cadence (or just Cadence globally).
Create React App / Vite — add to public/index.html:
<head>
<!-- other tags... -->
<script
src="https://cdn.softpath.co/sdk/v1/cadence.min.js"
data-project="YOUR_PROJECT_ID"
data-api-url="https://cdn.softpath.co"
></script>
</head>
Get your Project ID
Find your Project ID (like proj_Ax8mK2pQr4nB) on your project Settings page in the CADENCE dashboard, under the Quick Install tab.
TypeScript type declaration
Add a declaration file so TypeScript recognizes window.Cadence:
// src/cadence.d.ts (create this file)
import type CadenceClient from '@cadence-ab/sdk'
declare global {
interface Window {
Cadence?: CadenceClient
}
}
If you prefer to keep it simple, just use (window as any).Cadence in your components.
Running a test
Use useEffect + useState to read the variant after the SDK initializes. getVariant() is synchronous once ready() resolves.
import { useState, useEffect } from 'react'
function HeroSection() {
const [variant, setVariant] = useState<string>('control')
useEffect(() => {
if (!window.Cadence) return
window.Cadence.ready().then(() => {
const v = window.Cadence!.getVariant('hero-headline-test')
setVariant(v)
})
}, [])
switch (variant) {
case 'urgency':
return <h1>Limited Time Offer — Start Free Today</h1>
case 'social-proof':
return <h1>Join 10,000+ Teams Already Testing</h1>
default:
return <h1>Start Your Free Trial</h1>
}
}
The useEffect runs after mount, so there may be a brief flash of the default variant while the SDK initializes. If that's a problem, see the loading state pattern below.
Showing a loading state while the SDK initializes
import { useState, useEffect } from 'react'
function HeroSection() {
const [variant, setVariant] = useState<string | null>(null)
useEffect(() => {
if (!window.Cadence) {
setVariant('control')
return
}
window.Cadence.ready().then(() => {
const v = window.Cadence!.getVariant('hero-headline-test')
setVariant(v)
})
}, [])
// Don't render until we know the variant — prevents flash
if (variant === null) return <div className="h-16" />
switch (variant) {
case 'urgency':
return <h1>Limited Time Offer — Start Free Today</h1>
case 'social-proof':
return <h1>Join 10,000+ Teams Already Testing</h1>
default:
return <h1>Start Your Free Trial</h1>
}
}
Tracking conversions
Call window.Cadence?.track() or window.Cadence?.trackConversion() from any event handler:
function PricingButton() {
function handleClick() {
window.Cadence?.track('pricing-click', { plan: 'pro' })
}
return <button onClick={handleClick}>Upgrade to Pro</button>
}
Use trackConversion for primary goal events (signups, purchases). Use track for custom events (page views, scroll depth, button clicks you want to analyze but aren't the primary conversion goal).
function SignupForm() {
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
await submitForm()
window.Cadence?.trackConversion('signup-complete', { plan: 'free' })
}
return <form onSubmit={handleSubmit}>...</form>
}
Anti-flicker in React
The SDK's enableAntiFlicker() method hides the entire page (document.documentElement.style.visibility = 'hidden') until mutations are applied. In React, this can cause problems because React renders into a root <div> after mount, which means the page may stay hidden longer than expected.
The recommended approach in React is to use the loading state pattern shown above — render nothing (or a skeleton) until ready() resolves. This achieves the same effect without hiding the whole page.
Next.js specifics
In Next.js, add the script in app/layout.tsx using the built-in <Script> component:
import Script from 'next/script'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
{children}
<Script
src="https://cdn.softpath.co/sdk/v1/cadence.min.js"
data-project={process.env.NEXT_PUBLIC_CADENCE_PROJECT_ID}
data-api-url="https://cdn.softpath.co"
strategy="afterInteractive"
/>
</body>
</html>
)
}
Use strategy="afterInteractive" so the SDK loads after hydration.
In Client Components, access window.Cadence inside useEffect only — window is not available during server-side rendering. Always add 'use client' to components that use the SDK.
'use client'
import { useState, useEffect } from 'react'
export function CTAButton() {
const [variant, setVariant] = useState('control')
useEffect(() => {
window.Cadence?.ready().then(() => {
setVariant(window.Cadence!.getVariant('cta-button-test'))
})
}, [])
return (
<button
className={variant === 'green' ? 'bg-green-600' : 'bg-blue-600'}
onClick={() => window.Cadence?.track('cta-click')}
>
{variant === 'green' ? 'Start For Free' : 'Get Started'}
</button>
)
}
SDK method reference
| Method | Description |
|--------|-------------|
| window.Cadence.ready() | Returns a Promise that resolves when config is loaded — call this before getVariant() |
| window.Cadence.getVariant('name') | Get the user's variant for an experiment (tracks exposure automatically) |
| window.Cadence.track('name', props?) | Track a custom event |
| window.Cadence.trackConversion('name', props?) | Track a conversion/goal event |
| window.Cadence.isFeatureEnabled('key') | Check if a feature flag is enabled for this user |
| window.Cadence.getFeatureValue('key', default) | Get a feature flag's value |
| window.Cadence.enableAntiFlicker(ms?) | Hide page until mutations apply (not recommended in React — see above) |
| window.Cadence.destroy() | Flush events and clean up (call in useEffect cleanup if needed) |
Next steps
- Script Tag Setup — Full script tag reference with all attributes
- SDK Reference — Complete API documentation
- Creating Tests — Set up experiments in the dashboard
- Lovable Integration — For AI-built React apps