Record SDK for React

On this page
  1. Install the package
  2. Add the record button
  3. Use your own button with useDadanRecorder
  4. Play the recording with DadanEmbed
  5. Next.js
  6. Build a custom recorder in React
  7. Troubleshooting

@dadan-io/record-sdk-react adds Dadan recording to a React app with three building blocks: a ready-made record button, a hook for when you want your own button, and a component that plays the finished video. It works with React 18 and 19, including Next.js.

New to the SDK? Read the Record SDK overview first and make sure you have a public app ID with your development origin allowlisted.

bash
npm install @dadan-io/record-sdk-react

The package uses your app’s own react and react-dom (versions 18 or 19), so you do not install a second copy. With pnpm or Yarn, check that both are listed in your package.json.

Keep the public app ID in an environment variable, for example VITE_DADAN_PUBLIC_APP_ID in Vite or NEXT_PUBLIC_DADAN_PUBLIC_APP_ID in Next.js. It is safe in frontend code.

DadanRecordButton renders a Dadan record button. Clicking it opens the built-in studio, and onInsert receives the video once the upload has finished.

tsx
import {
  DadanRecordButton,
  type CreateInstanceArgs,
  type DadanVideo,
} from "@dadan-io/record-sdk-react";

const sdk: CreateInstanceArgs = {
  mode: "custom",
  publicAppId: import.meta.env.VITE_DADAN_PUBLIC_APP_ID,
  videoTitle: "Bug report",
  folderId: "123456",
};

export function BugReportRecorder({
  onRecorded,
}: {
  onRecorded: (video: DadanVideo) => void;
}) {
  return (
    <DadanRecordButton
      sdk={sdk}
      label="Record a bug report"
      onInsert={onRecorded}
      onCancel={() => console.info("Recording cancelled")}
      onError={(error) => console.error("Recording failed", error)}
    />
  );
}

How the button behaves:

  • It shows the Dadan mark on a 44 by 44 pixel button. label becomes its accessible name and tooltip. The default label is “Record with Dadan”.
  • It is disabled automatically in browsers that cannot record, and its tooltip explains why.
  • Changing values in sdk (for example folderId or config) rebuilds the studio, but only while it is closed, so a take in progress is never lost. Passing a new object with the same values does nothing, so inline objects are fine.
  • Standard button props such as className and disabled are passed through.

You can restyle the button with CSS custom properties on the button or any parent:

Custom propertyDefault
--dadan-sdk-btn-bg#4A3CFA
--dadan-sdk-btn-bg-hover#3527E0
--dadan-sdk-btn-fg#fff
--dadan-sdk-btn-radius12px

For a text button with your own design, use the hook below instead.

Use your own button with useDadanRecorder

Link to section: Use your own button with useDadanRecorder

useDadanRecorder creates the studio and gives you its state, so any element can open it:

tsx
import {
  useDadanRecorder,
  type CreateInstanceArgs,
} from "@dadan-io/record-sdk-react";

const sdk: CreateInstanceArgs = {
  mode: "custom",
  publicAppId: import.meta.env.VITE_DADAN_PUBLIC_APP_ID,
};

export function RecordAnswer() {
  const { ready, supported, open, lastVideo, error } =
    useDadanRecorder(sdk);

  if (!supported) {
    return <p>Video recording is not available in this browser.</p>;
  }

  return (
    <div>
      <button
        type="button"
        className="btn btn-primary"
        disabled={!ready}
        onClick={open}
      >
        Record your answer
      </button>
      {error && (
        <p role="alert">
          The recorder could not start. Please reload the page.
        </p>
      )}
      {lastVideo && (
        <p>
          Recorded. <a href={lastVideo.sharedUrl}>Watch your video</a>
        </p>
      )}
    </div>
  );
}

The hook returns:

ValueMeaning
readytrue once the studio can open
supportedfalse when the browser cannot record
supportErrorWhy recording is not supported, when it is not
errorSet if the studio failed to load
open, closeOpen or close the studio
lastVideoThe last DadanVideo uploaded through this studio
embedHtmlPlayer HTML for lastVideo
instanceThe underlying studio, for events and open({ mode })

To open a specific mode, call instance?.open({ mode: "camera" }).

The studio already shows its own progress screen. If you also want progress in your UI, subscribe to the studio’s events once it is ready:

tsx
import { useEffect, useState } from "react";
import {
  useDadanRecorder,
  type CreateInstanceArgs,
} from "@dadan-io/record-sdk-react";

export function useUploadProgress(sdk: CreateInstanceArgs) {
  const recorder = useDadanRecorder(sdk);
  const [percent, setPercent] = useState<number | null>(null);
  const { ready, instance } = recorder;

  useEffect(() => {
    if (!ready || !instance) return;
    const offProgress = instance.contractEmitter.on(
      "upload.progress",
      (p) => setPercent(p.percent),
    );
    const offDone = instance.contractEmitter.on("recording.completed", () =>
      setPercent(null),
    );
    return () => {
      offProgress();
      offDone();
    };
  }, [ready, instance]);

  return { ...recorder, percent };
}

Each on call returns a function that removes the listener, which fits React’s effect cleanup.

tsx
import { DadanEmbed } from "@dadan-io/record-sdk-react";

export function RecordingPlayer({ sharedUrl }: { sharedUrl: string }) {
  return (
    <DadanEmbed
      sharedUrl={sharedUrl}
      width={640}
      className="recording-player"
    />
  );
}

DadanEmbed renders a responsive player iframe for a share URL. It builds the player HTML in the browser and never inserts HTML returned by a server. For other ways to show or download a video, see Embed and download recordings.

The package entry points are marked "use client", but the callbacks you pass (onInsert, onError) are functions, so put the recorder in your own client component:

tsx
"use client";

import {
  DadanRecordButton,
  type DadanVideo,
} from "@dadan-io/record-sdk-react";

export function RecordButton() {
  function handleInsert(video: DadanVideo) {
    void fetch("/api/recordings", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        videoId: video.id,
        sharedUrl: video.sharedUrl,
      }),
    });
  }

  return (
    <DadanRecordButton
      sdk={{
        mode: "custom",
        publicAppId: process.env.NEXT_PUBLIC_DADAN_PUBLIC_APP_ID,
      }}
      onInsert={handleInsert}
    />
  );
}

You can render RecordButton from a server component. The studio only starts in the browser, after the component mounts.

The studio adds about 90 KB of gzipped JavaScript including React 18 (measured for version 1.0 with a production build). If the recorder is not needed on first load, import it lazily from a client component:

tsx
"use client";

import dynamic from "next/dynamic";

const RecordButton = dynamic(
  () => import("./record-button").then((m) => m.RecordButton),
  {
    ssr: false,
    loading: () => (
      <button type="button" disabled>
        Record
      </button>
    ),
  },
);

export function FeedbackForm() {
  return (
    <form>
      <RecordButton />
    </form>
  );
}

If you need your own recorder screens, use DadanRecorder, which this package re-exports. Create it in an effect and destroy it on unmount:

tsx
import { useEffect, useRef, useState } from "react";
import { DadanRecorder } from "@dadan-io/record-sdk-react";

export function CameraRecorder({ publicAppId }: { publicAppId: string }) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [recorder, setRecorder] = useState<DadanRecorder | null>(null);
  const [recording, setRecording] = useState(false);

  useEffect(() => {
    let active: DadanRecorder | undefined;
    let cancelled = false;
    void DadanRecorder.create({ publicAppId, mode: "camera" }).then((r) => {
      if (cancelled) {
        r.destroy();
        return;
      }
      active = r;
      r.on("preview.ready", ({ stream }) => {
        if (videoRef.current) videoRef.current.srcObject = stream;
      });
      r.on("recording.started", () => setRecording(true));
      r.on("recording.stopped", () => setRecording(false));
      setRecorder(r);
    });
    return () => {
      cancelled = true;
      active?.destroy();
    };
  }, [publicAppId]);

  return (
    <div>
      <video ref={videoRef} autoPlay muted playsInline />
      <button
        type="button"
        disabled={!recorder}
        onClick={() => (recording ? recorder?.stop() : recorder?.start())}
      >
        {recording ? "Stop" : "Start recording"}
      </button>
    </div>
  );
}

The full method, event and hook reference is in Build a custom recorder with DadanRecorder.

  • The button stays disabled. The browser cannot record: the page is not served over HTTPS or localhost, or a required browser API is missing. The button’s tooltip shows the reason.
  • “Couldn’t start recording” when the studio opens. The page’s origin is not on your public app’s allowlist. See Record SDK errors and troubleshooting.
  • Two copies of React. If you see invalid hook call errors, make sure only one react version is installed (npm ls react).

Ask AI about this article

What should the AI help you with?

Are you still facing issues?

Reach out to our support team for further assistance.

dadan