React
Run a measurement from a React component with the useNeccessoryMeasurement hook.
The package ships a second entry point for React. It wraps the same engine and the
same facade as createMeasurement — only the plumbing differs: refs instead of
elements, state instead of callbacks.
import { useNeccessoryMeasurement } from '@neccessory/web-sdk/react';React 18 or newer is a peer dependency. Everything on this page applies equally to the plain API described in the Quickstart.
Minimal component
import { useNeccessoryMeasurement } from '@neccessory/web-sdk/react';
export function Measure({ keyId }) {
const m = useNeccessoryMeasurement({ keyId });
return (
<div>
<video ref={m.videoRef} playsInline muted autoPlay />
<canvas ref={m.previewRef} />
<canvas ref={m.overlayRef} />
{m.phase === 'running' && <p>{m.realtime.heartRate ?? '--'} bpm</p>}
{m.phase === 'error' && <p>{m.error.message}</p>}
<button onClick={() => m.start()} disabled={m.phase === 'running'}>Start</button>
<button onClick={m.stop} disabled={m.phase !== 'running'}>Stop</button>
</div>
);
}As in the plain API, the secret is not required here: leave it out and forward
m.submitInput to your own backend, which signs and submits it.
The second argument
The hook takes two arguments. The first is your credentials, the second is everything about how the measurement runs — including which tier:
const m = useNeccessoryMeasurement(creds, options);| Option | Default | Meaning |
|---|---|---|
mode | 'on-device' | 'on-device' or 'cloud'. |
modelGrant | — | On-device only: grant from POST /sdk/v1/models/grant. |
session | — | Cloud only: the whole data object from POST /sdk/v1/cloud/sessions. |
sessionToken / sessionId / wsUrl | from session | Cloud only: the same fields passed individually. Each overrides session. |
delivery | 'auto' | Cloud only: 'stream', 'batch' or 'auto'. |
apiOrigin | https://neccessory.com | Cloud only: where the batch-fallback endpoints live. |
assetOrigin | https://neccessory.com | Where our assets are served from. |
A cloud component therefore looks like this — no credentials at all, because the session token carries the authorization and there is nothing to submit:
export function CloudMeasure({ session }) {
const m = useNeccessoryMeasurement(undefined, {
mode: 'cloud',
session,
delivery: 'auto',
});
return (
<div>
<video ref={m.videoRef} playsInline muted autoPlay />
<canvas ref={m.previewRef} />
{m.phase === 'running' && <p>{m.realtime.heartRate ?? '--'} bpm</p>}
{m.stage === 'uploading' && <p>Uploading…</p>}
{m.stage === 'processing' && <p>Processing…</p>}
<button onClick={() => m.start()} disabled={m.phase === 'running'}>Start</button>
</div>
);
}The full option reference, including aux and maxDurationSec on the plain API,
is in Configuration.
What the hook returns
| Field | Type | Meaning |
|---|---|---|
phase | 'idle' | 'loading' | 'running' | 'done' | 'error' | Coarse lifecycle state. Drive your UI from this rather than from callbacks. |
stage | string | null | Fine-grained progress within phase: 'requestingCamera', 'loadingModels', 'preparingWorkers', 'initializingSdk', 'ready' and, in cloud mode, 'connecting', 'uploading', 'processing'. Resets to null on every start(). |
realtime | { heartRate, quality } | Live values during capture; resets between runs. |
result | MeasurementResult | null | The finished measurement. |
submitInput | MeasurementInput | null | The result trimmed to the request body your backend should sign. |
error | { code, message } | null | Last failure. |
videoRef | ref | Attach to a <video>. Required. |
previewRef | ref | Optional canvas for the camera preview. |
overlayRef | ref | Optional canvas for the face overlay. |
start() | () => Promise<void> | Opens the camera and begins. |
stop() | () => void | Stops and releases the camera. |
stop() during a cloud fallback does not drop you back to idle: while stage
is 'uploading' or 'processing' the result is still on its way over HTTP, so
phase stays 'running' until it lands. Keep your "in progress" UI up for that
window rather than treating stop() as the end of the run.
Mounting the canvases conditionally
A common layout mounts the canvases only while the measurement is live:
{(m.phase === 'running' || m.phase === 'loading') && (
<>
<canvas ref={m.previewRef} />
<canvas ref={m.overlayRef} />
</>
)}That is supported — the hook hands the canvases to the engine as soon as they
appear, not once at start(). The <video> element is different: it must be
mounted before start() is called, otherwise the call fails with no_video.
Keep it in the tree and hide it with CSS if you only want to show the canvases.
Cleanup
The hook stops the measurement and releases the camera when the component unmounts. You do not need an effect for it.
Next: Callbacks reference.