Circadify

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.

Note

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.0
  • three (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';
ts

Without 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>
  );
}
jsx

Provider Props

PropTypeDefaultDescription
apiKeystring--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.
onDeviceOnlybooleantrueUse the standard local capture and measurement payload preparation path. Results are still returned through the Circadify API.
Tip

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)}
    />
  );
}
jsx

Component Props

PropTypeDefaultDescription
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 (idlereadinessscanningprocessinguploadingresults | error).
demographicsDemographics--{ age?, sex?, fitzpatrick? }. Optional, improves accuracy.
autoStartbooleanfalseStart scanning immediately when the component mounts.
showReadinessbooleantrueRender the Readiness step (camera check + 4-channel quality pills) before scanning.
showResultsbooleantrueRender the Results view after a successful scan. Set false if you want to render results in your own UI via onResult.
showFeedbackbooleantrueRender the live quality meters and status banner during scanning.
resultsActionsReactNode--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.
classNamestring--CSS class applied to the root .cf-card element.
styleReact.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>
      )}
    </>
  );
}
jsx

Composable 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.

ComponentPurpose
CircadifyReadinessCamera Check view: video preview, 4-channel quality pills (Face / Light / Still / Forward), optional gated Start button, in-frame loading/error overlay.
CircadifyScanViewActive-scan view: video + R3F thermal-glow overlay + quality meters + carded progress bar + processing-state spinner.
CircadifyResultsPost-scan metric stack with per-card clipboard copy, an actions slot, and an onRescan shortcut.
Want the face / heat-glow overlay? Use CircadifyScanView — don't rebuild it

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 })}
    />
  );
}
jsx

Using 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.
}
jsx

useSession(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>
  );
}
jsx

Return values:

PropertyTypeDescription
sessionSession | nullThe 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.
resultVitalSignsResult | nullThe scan result once complete.
errorCircadifyError | nullThe error if the session failed.
progressnumberProgress value from 0 to 1.
isScanningbooleanWhether 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 }) -- encapsulated getUserMedia lifecycle with permission and timeout handling. Returns { status, error, start, stop } with errors mapped to typed CircadifyError codes.
  • summarizeQuality({ state, faceDetected }) and detailFor(channel, state, faceDetected) -- derive the four-channel UI summary (Face / Light / Still / Forward) from the SDK's QualityState events. Use these to render your own quality pills or banners.
  • formatVitals(result) and metricToClipboard(metric) -- produce metric-card-ready payloads from a VitalSignsResult, 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>
  );
}
jsx

Cleanup

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();
jsx

Next Steps