Hooks
The headless surface from @dialogueai/react. Hooks are thin reactive views onto the core; they hold no state of their own.
useInterview(). The rest exist to drive custom UIs. For the exact shape of every referenced type (Study, ParticipantUpdate, ScreenerQuestion, TranscriptSegment, …) see the Types reference — the hook signatures here are abbreviated for readability.Lifecycle
useInterview(studyId?)
The top-level lifecycle hook, the only one a drop-in needs. Passing studyId auto-opens the study on mount.
{
phase: Phase;
study: Study | null;
// The error EVENT payload — not the DialogueError class. No `status`/`raw`.
error: { code: ErrorCode; message: string; recoverable: boolean } | undefined;
completionToken: string | null; // null even on `completed` until end-tokens ship
start(): void; // deviceCheck → connecting
end(): void; // end a live interview
retry(): void; // retry a recoverable error
on: <K>(event: K, cb) => Unsubscribe;
}useDialogue()
Coarse provider/SDK status plus the authenticated userId and the underlying core.
{ status: 'loading' | 'ready' | 'error'; userId: string | null; error; core }useStudy()
The loaded study, its public config, and the media requirements (camera/mic/screen).
{ study; studyPublic; requirements; isLoading: boolean; error }Journey steps
useConsent()
Drives the consent phase. accept() creates the interview record and loads the screener.
{
accepted: boolean;
accept(consentId?: string): void; // creates the interview + fetches screener
decline(): void;
showCompany: boolean;
company: Company | null;
hasScreener: boolean;
}useParticipantProfile()
Collects profile fields with an immediate per-step PATCH. Fields include firstName, lastInitial (a single char, not a last name), email, gender, dob, and location.
{
profile: ParticipantUpdate;
update(fields: ParticipantUpdate): Promise<void>; // immediate PATCH
next(): void; // advance past profile
isSaving: boolean;
error: unknown;
}useScreener()
The screener question flow with eligibility. submit() advances through questions and, on the last one, submits all responses.
{
hasScreener: boolean;
questions; currentIndex; question;
answer: string[];
setAnswer(optionId: string): void;
setInput(optionId: string, text: string): void; // custom "other" text
canContinue: boolean;
canGoBack: boolean; back(): void;
submit(): Promise<void>; // advances; on last question, submits responses
screenedOut: boolean;
redirectUrl: string | null;
}useAnonymousNames(locale)
Up to 10 locale-appropriate pseudonyms for the anonymous name picker. The argument is AnonymousNamesLocale — a lowercase code ('en', 'es', 'ja', …), not the uppercase DialogueLocale. Passing 'EN' will not match.
(locale: AnonymousNamesLocale) => { names: string[]; isLoading: boolean; error: unknown }Media & connection
useConnection()
LiveKit connection state and link quality. LiveKit auto-reconnects, so reconnect() is a no-op placeholder. Host code reaches the connection only through this hook.
{ state: 'idle' | 'connecting' | 'connected'; quality: ConnectionQuality; reconnect() }useMediaControls()
In-call mic / camera toggles. Camera state is derived from the published local track.
{
cameraOn: boolean; micMuted: boolean;
setCameraEnabled(enabled: boolean): Promise<void>;
setMicEnabled(enabled: boolean): Promise<void>;
toggleCamera(): void; toggleMic(): void;
}useScreenShare()
Per-gesture screen sharing. When a study requires it, connecting holds until confirm() publishes the share.
{
active; supported; usable; required; awaiting;
confirm(): Promise<void>; // satisfies a required-share gate, then goes live
start(audio?: boolean): Promise<void>;
stop(): Promise<void>;
}useRecording()
Read-only recording state (recording is server-side) with a live elapsed timer.
{ recording: boolean; durationSecs: number }useDeviceCheck()
Live mic VU meter, camera preview track, and device enumeration for the device-check screen.
{ vuLevel: number; videoTrack; devices: MediaDeviceInfo[]; selectedDeviceId; selectDevice }Locale, agent, feedback
useLocale()
The active locale (undefined until resolved). Locks at interview creation; set() is a no-op after that. See the 17 locale codes and the default-resolution rule.
{ locale: DialogueLocale | undefined; available: DialogueLocale[]; locked: boolean; set(l: DialogueLocale): void }useAgent()
Advanced: AI-agent speaking state, live transcript, and the current study section.
{ speaking: boolean; transcript: TranscriptSegment[]; sectionIndex: number; on }useFeedback()
Submit post-interview feedback.
{ submit({ title, description, currentUrl? }): Promise<void>; isSubmitting; error }useTelemetry()
Emit a lifecycle telemetry event (recorded as a breadcrumb).
(event: { type: string; [k]: unknown }) => voidBrowser capability
useDevicePermissions()
Camera + microphone permission state and a request trigger (not screen share). Seeds from the Permissions API where supported.
{
camera: 'prompt' | 'granted' | 'denied';
microphone: 'prompt' | 'granted' | 'denied';
request(opts?: { audio?: boolean; video?: boolean }): Promise<void>;
}usePreflight()
One-shot capability check before consent. A non-empty blocking array means the browser can’t run the interview (no getUserMedia, no WebRTC, or a nested iframe without allow=).
{ capabilities: { hasUserMedia; hasDisplayMedia; hasRTC }; blocking: string[] }Low-level access
For advanced cases, the store and context are exposed directly:
import { useCoreState, useMediaState, useDialogueContext } from '@dialogueai/react';
const core = useCoreState(); // full CoreState snapshot (re-renders on change)
const media = useMediaState(); // MediaState: quality, speaking, transcript, tracks…
const { core, controller, media, reporter } = useDialogueContext();splitName(name, anonymous?) is exported to build { firstName, lastInitial } from a display name the normative way.