Learn about Callback contract payload shape, and JavaScript vs React usage for onSubmit, onCancel, and onDraft in the Dictation SDK
Quick summary
The Suki Dictation SDK supports three callbacks: onSubmit, onCancel, and onDraft. These callbacks are called when the user commits, cancels, or drafts the dictation respectively. You can use these callbacks to handle the dictation result, cancel the session, or receive intermediate updates while the user is dictating.
Callbacks allow your application to run your own code when the user commits, cancels, or moves on without committing during dictation. You supply functions; the Suki Dictation SDK calls them when those events occur. You do not poll on a timer or in a loop for new text.Registering callbacksWhen you start a session, register them in JavaScript as properties on the object you pass to DictationClient.show(), or in React as props on <Dictation>.Callback executionIt calls onSubmit after the user commits, and onCancel if they leave without committing. If you supply onDraft, the SDK may call it when the user does not submit and switches to another field (or otherwise moves on without committing). onDraft is not invoked on every live transcript change while dictation stays on one field. The SDK does not persist draft text to your systems; only your onDraft handler decides whether to store or send it to your partner backend.PayloadEvery callback receives the same argument: one object, { fieldId, text }. The fieldId value is whatever you passed into show() or <Dictation>. A common pattern is to set it to the same value as the target control’s id, so onSubmit can use document.getElementById(fieldId) and write text into that element.
Optional. May run when the user leaves dictation without submitting and switches context (for example, to another field). Use it if you want to capture unstaged transcript text; the SDK does not save or forward it unless you do.
Mode controls how dictation is shown. Set it to in-field when the UI is tied to a container on your page, or scratchpad for a standalone surface. Refer to In-field mode and Scratchpad mode guides for behavior and layout.
For every other option you pass to DictationClient.show() or to <Dictation> as props, including rootElement, initialText, and these callbacks, use the Configuration reference.
This is the string you invent to name this dictation session. The SDK includes it in every callback so you know which field the text is for. Using the same text as your input’s id (for example clinical-notes) is a simple way to tie them together.
The transcript string for this callback (committed result, state at cancel, or interim draft, depending on which handler ran).
Example:
JavaScript
// After the user commits, your onSubmit might receive:{ fieldId: "clinical-notes", text: "The user said hello.",}
For structured notes, use stable, unique ids per section, for example soap.subjective, soap.objective, soap.assessment, soap.plan, or scratchpad for a floating session. See Field IDs.
If onSubmit is missing, dictation often closes immediately after the user acts. Always implement onSubmit.
Use callbacks as properties on the object you pass to await dictationClient.show({ ... }). They sit alongside mode, fieldId, rootElement, initialText, and other options from Configuration.The iframe resizes to the container you pass as rootElement when you use in-field mode.
JavaScript
import { SukiAuthManager } from "@suki-sdk/core";import { DictationClient } from "@suki-sdk/dictation";const authManager = new SukiAuthManager({ partnerId: "YOUR_PARTNER_ID", // Required partnerToken: "YOUR_PARTNER_TOKEN", // Required environment: "staging", // Optional loginOnInitialize: true, autoRegister: false, // Optional - default is false providerId: "YOUR_PROVIDER_ID", // Optional - required if autoRegister is true providerName: "YOUR_PROVIDER_NAME", // Optional - required if autoRegister is true providerOrgId: "YOUR_PROVIDER_ORG_ID", // Optional - required if autoRegister is true providerSpecialty: "YOUR_PROVIDER_SPECIALTY", // Optional - required if autoRegister is true});const dictationClient = new DictationClient({ authManager });// Example markup for in-field:// <div id="clinical-notes-dictation-root"></div>// <textarea id="clinical-notes"></textarea>await dictationClient.show({ mode: "in-field", fieldId: "clinical-notes", // same string as id="clinical-notes" on your textarea rootElement: document.getElementById("clinical-notes-dictation-root"), initialText: document.getElementById("clinical-notes")?.value ?? "", onSubmit: ({ fieldId, text }) => { const el = document.getElementById(fieldId); if (el) el.value = text; }, onCancel: ({ fieldId, text }) => { // Optional: user cancelled without committing }, onDraft: ({ fieldId, text }) => { // Optional: e.g. user switched fields without submit; persist draft in your app if needed },});
Calling show() again replaces the active session; you do not need to call hide() between fields for that pattern. Wrap show() in try / catch if you want to log configuration or auth failures.Refer to the Error handling guide for more details.
For more on imperative show() and one client per page scope, refer to the JavaScript integration guide for more details.
Pass the same three handlers as props on <Dictation>. Build DictationClient once (with SukiAuthManager), wrap your tree with <DictationProvider client={client}>, then render Dictation where dictation should be active.When Dictationunmounts, the SDK calls hide() for you.
React
import { useMemo, useState } from "react";import { SukiAuthManager } from "@suki-sdk/core";import { DictationClient } from "@suki-sdk/dictation";import { DictationProvider, Dictation } from "@suki-sdk/dictation-react";function NotesApp() { const client = useMemo(() => { const authManager = new SukiAuthManager({ partnerId: "YOUR_PARTNER_ID", // Required partnerToken: "YOUR_PARTNER_TOKEN", // Required environment: "staging", // Optional loginOnInitialize: true, autoRegister: false, // Optional - default is false providerId: "YOUR_PROVIDER_ID", // Optional - required if autoRegister is true providerName: "YOUR_PROVIDER_NAME", // Optional - required if autoRegister is true providerOrgId: "YOUR_PROVIDER_ORG_ID", // Optional - required if autoRegister is true providerSpecialty: "YOUR_PROVIDER_SPECIALTY", // Optional - required if autoRegister is true }); return new DictationClient({ authManager }); }, []); const [notes, setNotes] = useState(""); // Drive from your UI: e.g. setActiveField("clinical-notes") when this field owns dictation. const [activeField, setActiveField] = useState(null); const handleSubmit = ({ fieldId, text }) => { // fieldId matches id="clinical-notes" below if you use that pattern setNotes(text); }; const handleCancel = ({ fieldId, text }) => { // Optional }; const handleDraft = ({ fieldId, text }) => { // Optional: e.g. save unstaged text when user leaves without submit }; return ( <DictationProvider client={client}> <textarea id="clinical-notes" value={notes} onChange={(e) => setNotes(e.target.value)} /> {activeField === "clinical-notes" && ( <Dictation fieldId="clinical-notes" mode="in-field" initialText={notes} onSubmit={handleSubmit} onCancel={handleCancel} onDraft={handleDraft} /> )} </DictationProvider> );}
If you already create client higher in the tree, pass it into DictationProvider the same way. Use state such as activeField so only one field owns the session.Refer to React integration guide for more details.