React
Integrate the Circadify Web SDK into your React application with the built-in React bindings.
This guide walks you through integrating Circadify into a React application using the React bindings that ship with the Web SDK.
The React components and hooks ship inside @circadify/web-sdk at the @circadify/web-sdk/react subpath — there is no separate package to install. Follow the Web SDK installation guide to get GitHub Packages access and run npm install @circadify/web-sdk; that single install gives you both the headless SDK and the React bindings.
The React bindings use these optional peer dependencies — install whichever your project doesn't already have. They are optional on the Web SDK, so headless (non-React) consumers are not required to install them:
react@>=18.0.0,react-dom@>=18.0.0@react-three/fiber@^8.15.0,@react-three/drei@^9.88.0three(any 0.151 -- 0.168)
Styles
The React bindings ship an opt-in stylesheet for the bundled views (Readiness, Scan, Results). Import it once -- typically alongside your global CSS or in your app entry point:
import '@circadify/web-sdk/react/styles.css';tsWithout this import the components render unstyled. If you build a fully custom UI with the hooks only (never mounting <CircadifyScan> or the composable views), the stylesheet is optional.
Provider Setup
Wrap your application (or the relevant subtree) with the CircadifyProvider. This initializes the SDK and makes it available to all child components via React context.
import { CircadifyProvider } from '@circadify/web-sdk/react';
function App() {
return (
<CircadifyProvider apiKey="ck_live_your_key_here">
<YourApp />
</CircadifyProvider>
);
}jsxProvider Props
| Prop | Type | Default | Description |
|---|---|---|---|
apiKey | string | -- | Your Circadify API key (ck_live_*). Required. |
onProgress | (progress: ProgressEvent) => void | -- | Global callback for scan progress updates. |
onQualityWarning | (warning: QualityWarning) => void | -- | Called when lighting, movement, or face positioning issues are detected. |
onDeviceOnly | boolean | true | Use the standard local capture and measurement payload preparation path. Results are still returned through the Circadify API. |
Place the provider as high in the tree as needed, but not higher. If only one route uses scanning, wrap just that route to avoid initializing the SDK unnecessarily.
Using the Component
The CircadifyScan component renders a complete scanning interface with a camera feed, face guide overlay, progress indicator, and quality warnings. It handles the full session lifecycle automatically.
import { CircadifyScan } from '@circadify/web-sdk/react';
function ScanPage() {
const handleResult = (result) => {
console.log('Heart Rate:', result.heartRate);
console.log('Confidence:', result.confidence);
};
return (
<CircadifyScan
type="standard"
onResult={handleResult}
onError={(error) => console.error(error)}
/>
);
}jsxComponent Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'standard' | 'standard' | The scan session type. |
onResult | (result: VitalSignsResult) => void | -- | Called when a scan completes successfully. |
onError | (error: CircadifyError) => void | -- | Called when a scan fails. |
onProgress | (progress: ProgressEvent) => void | -- | Called during scan with progress updates. |
onPhaseChange | (phase: ScanPhase) => void | -- | Fires on every state-machine transition (idle → readiness → scanning → processing → uploading → results | error). |
demographics | Demographics | -- | { age?, sex?, fitzpatrick? }. Optional, improves accuracy. |
autoStart | boolean | false | Start scanning immediately when the component mounts. |
showReadiness | boolean | true | Render the Readiness step (camera check + 4-channel quality pills) before scanning. |
showResults | boolean | true | Render the Results view after a successful scan. Set false if you want to render results in your own UI via onResult. |
showFeedback | boolean | true | Render the live quality meters and status banner during scanning. |
resultsActions | ReactNode | -- | Slot rendered under the metric stack on the Results view (share/save buttons, external CTAs). |
onRescan | () => void | -- | When supplied, a default outlined "Scan Again" button is appended to the Results actions slot and the orchestrator resets to readiness on click. |
className | string | -- | CSS class applied to the root .cf-card element. |
style | React.CSSProperties | -- | Inline styles for the root element. |
Handling Results
The onResult callback receives a VitalSignsResult object:
function ScanPage() {
const [vitals, setVitals] = useState(null);
return (
<>
<CircadifyScan
type="standard"
demographics={{ age: 35, sex: 'M' }}
onResult={(result) => setVitals(result)}
onError={(error) => alert(error.message)}
/>
{vitals && (
<div>
<p>Heart Rate: {vitals.heartRate} BPM</p>
<p>SpO2: {vitals.spo2}%</p>
<p>Confidence: {(vitals.confidence * 100).toFixed(0)}%</p>
</div>
)}
</>
);
}jsxComposable Subcomponents
When the all-in-one <CircadifyScan> doesn't fit -- for example, you want a custom view between Readiness and Results, or you want to render Results inside a modal -- import the underlying views and pair them with useSession() (or your own state).
Each subcomponent is presentational and props-driven; none of them own SDK lifecycle.
| Component | Purpose |
|---|---|
CircadifyReadiness | Camera Check view: video preview, 4-channel quality pills (Face / Light / Still / Forward), optional gated Start button, in-frame loading/error overlay. |
CircadifyScanView | Active-scan view: video + R3F thermal-glow overlay + quality meters + carded progress bar + processing-state spinner. |
CircadifyResults | Post-scan metric stack with per-card clipboard copy, an actions slot, and an onRescan shortcut. |
CircadifyScanView already renders the thermal/heat-glow face overlay for you. Mount it instead of hand-rolling a three.js / @react-three/fiber overlay from onLandmarks.
If you genuinely need a custom overlay, prefer a 2D <canvas> in immediate mode (see the Custom UI guide) — it has no retained geometry to freeze. Only reach for r3f as a last resort, and if you do, never create BufferGeometry/BufferAttributes in useMemo (React discards it under load and the overlay renders once then freezes) — build them once via useState lazy-init, set frameloop="always", frustumCulled={false}, an orthographic camera at zoom={1}, a z-indexed overlay layer, and clear the overlay on empty-landmark frames. Landmarks are normalized [0,1] and not mirrored. Full checklist (incl. the @react-three/fiber v9 gotcha): Custom UI → Advanced: a 3D (r3f) overlay.
import {
CircadifyProvider,
CircadifyReadiness,
CircadifyResults,
useSession,
useCamera,
} from '@circadify/web-sdk/react';
import { useRef } from 'react';
import '@circadify/web-sdk/react/styles.css';
function CustomFlow() {
const session = useSession();
const videoRef = useRef(null);
const camera = useCamera({ videoRef });
if (session.result) {
return <CircadifyResults result={session.result} onRescan={() => session.start()} />;
}
return (
<CircadifyReadiness
videoRef={videoRef}
landmarks={null} /* leave null unless you render a custom overlay — prefer CircadifyScanView or a 2D canvas, not a hand-rolled r3f overlay */
quality={null}
cameraStatus={camera.status}
cameraError={camera.error}
onStart={() => session.start({ videoElement: videoRef.current })}
/>
);
}jsxUsing Hooks
For more control over the scanning lifecycle, use the SDK hooks directly. This is useful when you need a custom UI or want to integrate scanning into an existing component.
useCircadify()
Returns the underlying CircadifySDK instance from the nearest CircadifyProvider. Use this for direct SDK access.
import { useCircadify } from '@circadify/web-sdk/react';
function DebugPanel() {
const sdk = useCircadify();
// Access sdk.measureVitals(), sdk.destroy(), sdk.cancel(), sdk.getDeviceCapabilities(), etc.
}jsxuseSession(options)
Manages a scan session with start/stop controls and reactive state. Returns the session state, control functions, and result data.
import { useCircadify, useSession } from '@circadify/web-sdk/react';
function CustomScan() {
const client = useCircadify();
const { session, start, stop, result, error, progress, isScanning } = useSession({
type: 'standard',
});
return (
<div>
<button onClick={start} disabled={isScanning}>Start Scan</button>
<button onClick={stop} disabled={!isScanning}>Stop</button>
{isScanning && <p>Progress: {Math.round(progress * 100)}%</p>}
{error && <p>Error: {error.message}</p>}
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
);
}jsxReturn values:
| Property | Type | Description |
|---|---|---|
session | Session | null | The active session object, or null if not started. |
start | (opts?: UseSessionStartOptions) => Promise<void> | Start a new scan session. Optional opts: { signal?, demographics?, videoElement? }. Pass a videoElement to drive your own <video> instead of letting the SDK create one. |
stop | () => Promise<void> | Stop the active session. |
result | VitalSignsResult | null | The scan result once complete. |
error | CircadifyError | null | The error if the session failed. |
progress | number | Progress value from 0 to 1. |
isScanning | boolean | Whether a scan is currently in progress. |
Utility hooks and helpers
When you're driving the camera and feedback UI yourself, three additional helpers from @circadify/web-sdk/react make custom flows easier:
useCamera({ videoRef, timeoutMs })-- encapsulatedgetUserMedialifecycle with permission and timeout handling. Returns{ status, error, start, stop }with errors mapped to typedCircadifyErrorcodes.summarizeQuality({ state, faceDetected })anddetailFor(channel, state, faceDetected)-- derive the four-channel UI summary (Face / Light / Still / Forward) from the SDK'sQualityStateevents. Use these to render your own quality pills or banners.formatVitals(result)andmetricToClipboard(metric)-- produce metric-card-ready payloads from aVitalSignsResult, including pretty units and clipboard strings.
Cancellation with AbortController
You can pass an AbortController signal through the hook to allow cancellation:
function CancellableScan() {
const controllerRef = useRef(null);
const { start, stop, result } = useSession({ type: 'standard' });
const handleStart = () => {
controllerRef.current = new AbortController();
start({ signal: controllerRef.current.signal });
};
const handleCancel = () => {
controllerRef.current?.abort();
};
return (
<div>
<button onClick={handleStart}>Start</button>
<button onClick={handleCancel}>Cancel</button>
</div>
);
}jsxCleanup
The CircadifyProvider automatically calls sdk.destroy() when it unmounts, releasing camera streams, runtime resources, and event listeners. No manual cleanup is required in most cases.
If you need to manually destroy the SDK instance (for example, on logout), use the useCircadify hook:
const sdk = useCircadify();
sdk.destroy();jsxNext Steps
- Custom UI -- Build a fully custom interface
- SDK Methods -- Full method reference