@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.
Install the package
Link to section: Install the packagenpm install @dadan-io/record-sdk-reactThe 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.
Add the record button
Link to section: Add the record buttonDadanRecordButton renders a Dadan record button. Clicking it opens the built-in studio, and onInsert receives the video once the upload has finished.
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.
labelbecomes 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 examplefolderIdorconfig) 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
classNameanddisabledare passed through.
You can restyle the button with CSS custom properties on the button or any parent:
| Custom property | Default |
|---|---|
--dadan-sdk-btn-bg | #4A3CFA |
--dadan-sdk-btn-bg-hover | #3527E0 |
--dadan-sdk-btn-fg | #fff |
--dadan-sdk-btn-radius | 12px |
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 useDadanRecorderuseDadanRecorder creates the studio and gives you its state, so any element can open it:
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:
| Value | Meaning |
|---|---|
ready | true once the studio can open |
supported | false when the browser cannot record |
supportError | Why recording is not supported, when it is not |
error | Set if the studio failed to load |
open, close | Open or close the studio |
lastVideo | The last DadanVideo uploaded through this studio |
embedHtml | Player HTML for lastVideo |
instance | The underlying studio, for events and open({ mode }) |
To open a specific mode, call instance?.open({ mode: "camera" }).
Show upload progress
Link to section: Show upload progressThe 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:
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.
Play the recording with DadanEmbed
Link to section: Play the recording with DadanEmbedimport { 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.
Next.js
Link to section: Next.jsThe 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:
"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:
"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>
);
}Build a custom recorder in React
Link to section: Build a custom recorder in ReactIf you need your own recorder screens, use DadanRecorder, which this package re-exports. Create it in an effect and destroy it on unmount:
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.
Troubleshooting
Link to section: Troubleshooting- 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
reactversion is installed (npm ls react).