LIVE · AUDIT-CHAINED · EU-RESIDENT
SYSTEM · 99.99% UPTIME
v 1.0 ↗ MADE IN EU

@nexbasira/react

React wrapper around @nexbasira/embed. Two surfaces — a drop-in <NexBasiraSession> component for the common case, plus a useNexBasiraSession() hook for custom layouts.

Install

npm install @nexbasira/react @nexbasira/embed react

react and @nexbasira/embed are peer dependencies — they aren't bundled with this package.

Component

import { NexBasiraSession } from "@nexbasira/react";

function InspectionPage({ joinUrl }: { joinUrl: string }) {
  return (
    <NexBasiraSession
      sessionUrl={joinUrl}
      height="720px"
      onSessionComplete={(id) => navigate(`/inspections/${id}`)}
    />
  );
}

sessionUrl is the one-shot URL your backend gets back from sessions.invite() on the Node / Python SDK. The component mounts the iframe on first render and destroys it cleanly on unmount.

Imperative access via ref

Use a ref when the field-side controls live outside the iframe (toolbar above the component, modal trigger, etc.):

import { useRef } from "react";
import { NexBasiraSession, type NexBasiraSessionHandle } from "@nexbasira/react";

function Toolbar({ joinUrl }: { joinUrl: string }) {
  const session = useRef<NexBasiraSessionHandle>(null);
  return (
    <>
      <button onClick={() => session.current?.requestSnapshot()}>Capture</button>
      <button onClick={() => session.current?.openWhiteboard()}>Whiteboard</button>
      <button onClick={() => session.current?.endSession()}>End</button>
      <NexBasiraSession ref={session} sessionUrl={joinUrl} />
    </>
  );
}

Hook variant

When the wrapper <div> doesn't fit your layout — say you want the iframe in a flex container with a sidebar — use the hook + attach the returned ref to your own host element:

import { useNexBasiraSession } from "@nexbasira/react";

function CustomLayout({ joinUrl }: { joinUrl: string }) {
  const [containerRef, widget] = useNexBasiraSession({
    sessionUrl: joinUrl,
    onSessionComplete: (id) => navigate(`/inspections/${id}`),
  });

  return (
    <div className="grid grid-cols-[1fr_240px] gap-4">
      <div ref={containerRef} style={{ minHeight: 600 }} />
      <aside>
        <button onClick={() => widget.current?.requestSnapshot()}>Snap</button>
      </aside>
    </div>
  );
}

Lifecycle callbacks stay fresh across renders

The embed widget is created once per sessionUrl. To make sure your callbacks always see the latest closure (closing over the latest state), the wrapper internally stores them in a useRef and re-reads on every event. You can pass inline arrow functions safely:

function Page() {
  const [count, setCount] = useState(0);
  return (
    <NexBasiraSession
      sessionUrl={joinUrl}
      onEvidenceAdded={() => {
        // 'count' is always the latest value, not the value at mount
        setCount(count + 1);
      }}
    />
  );
}

When does the iframe rebuild?

Only when sessionUrl changes. Every other prop (callbacks, width, height, expectedOrigin) applies in-place without remounting. This means changing a callback or resizing the host doesn't restart the live session — useful when your app re-renders frequently.

Props

PropTypeNotes
sessionUrlstringRequired. Minted by your backend.
width / heightstringCSS sizes. Default 100% / 600px.
expectedOriginstringOverride iframe origin if hosting on a custom domain.
classNamestringApplied to the wrapper div.
styleCSSPropertiesApplied to the wrapper div.
on* callbacksfunctionSame surface as @nexbasira/embed — see that page.

Typed handle

import type { NexBasiraSessionHandle } from "@nexbasira/react";

const ref = useRef<NexBasiraSessionHandle>(null);
// ref.current has typed methods: requestSnapshot(), openWhiteboard(), etc.

What's next