NeccessoryNeccessory
Web SDK

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);
OptionDefaultMeaning
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 / wsUrlfrom sessionCloud only: the same fields passed individually. Each overrides session.
delivery'auto'Cloud only: 'stream', 'batch' or 'auto'.
apiOriginhttps://neccessory.comCloud only: where the batch-fallback endpoints live.
assetOriginhttps://neccessory.comWhere 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

FieldTypeMeaning
phase'idle' | 'loading' | 'running' | 'done' | 'error'Coarse lifecycle state. Drive your UI from this rather than from callbacks.
stagestring | nullFine-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.
resultMeasurementResult | nullThe finished measurement.
submitInputMeasurementInput | nullThe result trimmed to the request body your backend should sign.
error{ code, message } | nullLast failure.
videoRefrefAttach to a <video>. Required.
previewRefrefOptional canvas for the camera preview.
overlayRefrefOptional canvas for the face overlay.
start()() => Promise<void>Opens the camera and begins.
stop()() => voidStops 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.