> ## Documentation Index
> Fetch the complete documentation index at: https://developer.suki.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Form Filling SDK React Integration

> Integrate the Form filling SDK in a React application using FormFillingProvider and FormFilling

This guide walks you through integrating the Form filling SDK in a **React 18+** application. You create **`SukiAuthManager`** and **`FormFillingClient`** once inside **`useMemo`**, wrap your tree with **`FormFillingProvider`**, and render **`<FormFilling>`** when the clinician opens Form filling.

## Prerequisites

Before you begin, confirm you have:

* **Packages:** **`@suki-sdk/form-filling-react`** and **`@suki-sdk/core`**
* **Partner credentials:** **`partnerId`** and **`partnerToken`** from [Partner onboarding](/documentation/get-started/partner-onboarding)
* **Template IDs:** **`template_id`** UUIDs from your **Suki support team** for **`form_template_ids`**.
* **Encounter ID:** pass your encounter ID as **`correlation_id`**, refer to [Configuration](/form-filling-sdk/guides/configuration#correlation_id-your-encounter-id) for more details.
* **Browser and layout:** HTTPS, microphone access, and space for the Form filling UI (the **`.suki-form-filling`** container needs height)

Refer to [Prerequisites](/form-filling-sdk/prerequisites) for webhooks and CSP.

## Integration pattern

Create auth and the client **once per page** inside **`useMemo`**. Mount **`<FormFilling>`** only when the clinician is ready to open Form filling.

<CardGroup cols={2}>
  <Card title="Suki Auth Manager" icon="lock">
    One **`SukiAuthManager`** after the partner token is available.
  </Card>

  <Card title="Form Filling Client" icon="cube">
    One **`FormFillingClient`** in **`useMemo`**. Reuse it for every session on that page.
  </Card>

  <Card title="FormFillingProvider" icon="react">
    Wrap the part of your tree that renders **`<FormFilling>`**.
  </Card>

  <Card title="Conditional FormFilling" icon="file-lines">
    Mount **`<FormFilling>`** only while Form filling should be open.
  </Card>
</CardGroup>

<Note>
  Do not run **`new FormFillingClient(...)`** on every render. Use **`useMemo`** with stable dependencies.
</Note>

## Install the packages

<CodeGroup title="Install @suki-sdk/form-filling-react and @suki-sdk/core">
  ```shell pnpm theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  pnpm add @suki-sdk/form-filling-react @suki-sdk/core
  ```

  ```shell npm theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  npm install @suki-sdk/form-filling-react @suki-sdk/core
  ```

  ```shell yarn theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  yarn add @suki-sdk/form-filling-react @suki-sdk/core
  ```
</CodeGroup>

## Step 1: Style the container

React renders the hosted UI inside **`.suki-form-filling`**. Give that container explicit height, or the iframe looks blank.

```css CSS theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
.suki-form-filling {
  width: 100%;
  height: min(80vh, 720px);
  min-height: 400px;
}
```

<Tip>
  Refer to [Configuration](/form-filling-sdk/guides/configuration#container-sizing) for recommended CSS.
</Tip>

## Step 2: Create auth manager and client in useMemo

Create both objects once per page inside **`useMemo`**. Optionally wire **`onError`** on the client for configuration and runtime errors.

```tsx React expandable theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
import { useMemo } from "react";
import { SukiAuthManager } from "@suki-sdk/core";
import { FormFillingClient } from "@suki-sdk/form-filling-react";

export function EncounterPage({ encounterId }: { encounterId: string }) {
  const client = useMemo(() => {
    const authManager = new SukiAuthManager({
      partnerId: "YOUR_PARTNER_ID", // Required
      partnerToken: "YOUR_PARTNER_TOKEN", // Required
      environment: "staging", // Optional
      loginOnInitialize: true, // Optional
      autoRegister: false, // optional (default true); when true, provider fields below are often required
      providerId: "YOUR_PROVIDER_ID", // Optional
      providerName: "Dr. Jane Smith", // Optional
    });
    return new FormFillingClient({
      authManager, // Required
      onError: (err) => console.error(err.code, err.reason, err.message), // Optional
    });
  }, []);

  // Steps 3–4 below
}
```

<Tip>
  Refer to [Authentication](/form-filling-sdk/guides/authentication) for provider fields and other sign-in options.
</Tip>

## Step 3: Wrap with FormFillingProvider

Pass the **`client`** from Step 2 into **`FormFillingProvider`**. Every component that renders **`<FormFilling>`** must be a child of that provider.

```tsx React theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
import { FormFillingProvider } from "@suki-sdk/form-filling-react";

return (
  <FormFillingProvider client={client}>
    {/* children that render <FormFilling /> */}
  </FormFillingProvider>
);
```

You can place **`FormFillingProvider`** at app root or only around the encounter screen that needs Form filling.

## Step 4: Render FormFilling when the clinician opens Form filling

Use state (for example **`isFormOpen`**) so **`<FormFilling>`** mounts only while Form filling should be visible. When it unmounts, the SDK tears down the hosted UI.

Pass the same session fields you would pass to **`client.start()`**, except **`rootElement`** (React supplies the container):

* **`form_template_ids`**: <Badge color="red" size="sm">Required</Badge> **`template_id`** UUIDs from your Suki support team.
* **`correlation_id`**: <Badge color="green" size="sm">Optional</Badge> Your encounter or appointment ID. Strongly recommended so results map to the correct record.
* **`onSubmit`**: <Badge color="red" size="sm">Required</Badge> Called when structured JSON is ready after processing, not when the clinician taps submit. Receives a **`FormFillingResult`**.
* **`onReady`**, **`onCancel`**: <Badge color="green" size="sm">Optional</Badge>
* **`onBackgroundSubmit`**: <Badge color="green" size="sm">Optional</Badge> React only. Results for a superseded session while the iframe is still open.

```tsx React expandable theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
import { useState } from "react";
import { FormFilling } from "@suki-sdk/form-filling-react";

function FormFillingLauncher({ encounterId }: { encounterId: string }) {
  const [isFormOpen, setIsFormOpen] = useState(false);

  const handleSubmit = (result) => {
    for (const form of result.structured_data.generated_values) {
      console.log(form.form_template_id, form.data);
    }
    setIsFormOpen(false);
  };

  return (
    <>
      <button type="button" onClick={() => setIsFormOpen(true)} disabled={isFormOpen}>
        Fill Forms with Suki
      </button>

      {isFormOpen && (
        <FormFilling
          form_template_ids={["YOUR_TEMPLATE_ID"]} // Required
          correlation_id={encounterId} // Optional
          onReady={() => console.log("Ready")} // Optional
          onSubmit={handleSubmit} // Required
          onCancel={() => setIsFormOpen(false)} // Optional
          onBackgroundSubmit={handleSubmit} // Optional
        />
      )}
    </>
  );
}
```

## Callbacks and events

Your app needs to know when Form filling is ready, when structured results arrive, and when the clinician cancels. You can handle that in two ways.

**Option 1: Pass props on `<FormFilling>`**

Use this for logic tied to one session. Props mirror **`client.start()`** callbacks:

| Prop                     | When it runs                                                                                 |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| **`onReady`**            | The hosted UI loaded and is ready for the clinician                                          |
| **`onSubmit`**           | Structured JSON is ready after processing (map this to your EHR or show results in your app) |
| **`onCancel`**           | The clinician left without submitting                                                        |
| **`onBackgroundSubmit`** | Results for a superseded session while the iframe is still open (React only)                 |

**Option 2: Listen with `client.on()`**

Use this for handlers that stay active across many sessions, such as logging, analytics, or a shared error handler. Register once when you create the client in **`useMemo`**:

```tsx React theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
client.on("form-filling:submitted", (result) => {
  console.log(result.ambient_session_id);
});
```

Common events include **`form-filling:session-started`**, **`form-filling:background-submitted`**, and **`form-filling:error`**.

Refer to [Callbacks](/form-filling-sdk/guides/callbacks) for **`FormFillingResult`** shape and [Events](/form-filling-sdk/api-reference/events) for every event and payload.

## Starting another session

When the clinician opens Form filling again, mount **`<FormFilling>`** again (for example set **`isFormOpen`** back to **`true`**). You do not need a new **`FormFillingClient`**. Mount only one **`<FormFilling>`** at a time per **`client`**.

## useFormFilling hook

Inside **`FormFillingProvider`**, use **`useFormFilling()`** to read session state and errors in sibling components:

```tsx React theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
import { useFormFilling } from "@suki-sdk/form-filling-react";

function FormFillingStatus() {
  const { isSessionActive, error } = useFormFilling();

  // Prefer session state. The provider keeps the last error until the next
  // form-filling:error, so do not return early on error alone after a retry.
  if (isSessionActive) {
    return <p>Form session active</p>;
  }
  if (error) {
    return <p role="alert">Error ({error.code}): {error.message}</p>;
  }
  return null;
}
```

## Complete code example

Below is a full page example: sign-in, client setup in **`useMemo`**, provider wiring, and a button that opens Form filling.

```tsx React expandable theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
import { useMemo, useState } from "react";
import { SukiAuthManager } from "@suki-sdk/core";
import {
  FormFillingClient,
  FormFillingProvider,
  FormFilling,
} from "@suki-sdk/form-filling-react";

export function EncounterPage({ encounterId }: { encounterId: string }) {
  const client = useMemo(() => {
    const authManager = new SukiAuthManager({
      partnerId: "YOUR_PARTNER_ID", // Required
      partnerToken: "YOUR_PARTNER_TOKEN", // Required
      environment: "staging", // Optional
      loginOnInitialize: true, // Optional
      autoRegister: false, // optional (default true); when true, providerName and providerOrgId are often required
    });
    return new FormFillingClient({
      authManager, // Required
      onError: (err) => console.error(err.code, err.message), // Optional
    });
  }, []);

  const [isFormOpen, setIsFormOpen] = useState(false);

  const handleSubmit = (result) => {
    for (const form of result.structured_data.generated_values) {
      console.log(form.form_template_id, form.data);
    }
    setIsFormOpen(false);
  };

  return (
    <FormFillingProvider client={client}>
      <h1>Encounter {encounterId}</h1>
      <button type="button" onClick={() => setIsFormOpen(true)} disabled={isFormOpen}>
        Fill Forms with Suki
      </button>
      {isFormOpen && (
        <FormFilling
          form_template_ids={["YOUR_TEMPLATE_ID"]} // Required
          correlation_id={encounterId} // Optional
          onReady={() => console.log("Ready")} // Optional
          onSubmit={handleSubmit} // Required
          onCancel={() => setIsFormOpen(false)} // Optional
          onBackgroundSubmit={handleSubmit} // Optional
        />
      )}
    </FormFillingProvider>
  );
}
```

Your integration works when the Form filling UI appears, the microphone is active, and **`onSubmit`** returns **`structured_data`**.

## Best practices

<Tip>
  * **Reuse one client in `useMemo`.** Do not create **`FormFillingClient`** on every render.

  * **Mount one `<FormFilling>` at a time** per **`client`**.

  * **Pass `onSubmit`.** It is required. Map **`structured_data.generated_values`** to your EHR or backend.

  * **Pass `correlation_id`.** Use your encounter ID so callbacks and webhooks match the correct record.

  * **Use `onBackgroundSubmit`** when results arrive for a session the clinician has already replaced or reopened.

  * **Register a partner webhook** for production saves on your server, not browser **`onSubmit`** alone.

  * **Style `.suki-form-filling` with height** before the UI mounts. Zero height is the most common cause of a blank UI.

  * **Use template IDs from Suki support.** The SDK validates IDs when **`<FormFilling>`** mounts and drops unsupported values.
</Tip>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Blank Form Filling Area">
    Set CSS height on **`.suki-form-filling`**. Add CSP **`frame-src`** for **`https://sdk.suki.ai`** (production) or **`https://sdk.suki-stage.com`** (staging). Refer to [Configuration](/form-filling-sdk/guides/configuration#container-sizing) and [Prerequisites](/form-filling-sdk/prerequisites).
  </Accordion>

  <Accordion title="Session Resets or Flickers">
    Do not create **`FormFillingClient`** on every render. Keep one client in **`useMemo`** and pass the same instance to **`FormFillingProvider`**.
  </Accordion>

  <Accordion title="Multiple Sessions Conflict">
    Mount only one **`<FormFilling>`** at a time per **`client`**. Unmount the previous session before starting another.
  </Accordion>

  <Accordion title="`SUKI_FF_001` Immediately">
    **`form_template_ids`** is empty, or no IDs match your partner account. Pass at least one **`template_id`** UUID from Suki support. Refer to [Form filling templates](/documentation/concepts/form-filling/form-filling-templates).
  </Accordion>

  <Accordion title="`SUKI_FF_002` Handshake Timeout">
    The iframe did not complete the ready handshake within 10 seconds. Check network, ad blockers, and CSP. Confirm the page uses HTTPS.
  </Accordion>

  <Accordion title="`SUKI_FF_003` Fetch Templates Failed">
    Auth failed or the wrong environment (staging vs production) while loading the template list. Retry and confirm **`partnerId`**, **`partnerToken`**, and **`environment`**.
  </Accordion>

  <Accordion title="`SUKI_FF_004` Iframe Runtime Error">
    Microphone permission denied, session start/submit failed, or form generation failed. Read **`err.message`**, show a retry option, and confirm network access.
  </Accordion>

  <Accordion title="UI Never Opens">
    Sign-in failed before the hosted UI loads. Verify **`partnerId`** and **`partnerToken`** for your environment. Refer to [Authentication](/form-filling-sdk/guides/authentication).
  </Accordion>

  <Accordion title="No `onSubmit` Callback">
    The clinician closed the processing screen early, or the tab closed before processing finished. Register a [partner webhook](/documentation/webhook/overview) for server-side delivery.
  </Accordion>

  <Accordion title="`onSubmit` Never Maps to Your Encounter">
    Pass your encounter ID as **`correlation_id`** at session start so callbacks and webhooks match the correct record.
  </Accordion>

  <Accordion title="Late Results After Offline Submit">
    Handle **`onBackgroundSubmit`** on **`<FormFilling>`** or listen for **`form-filling:background-submitted`** on the client.
  </Accordion>

  <Accordion title="Log Errors During Development">
    Wire **`onError`** on the client, **`useFormFilling()`** **`error`**, or **`client.on("form-filling:error", ...)`**:

    ```tsx React theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    client.on("form-filling:error", (err) => {
      console.error(err.code, err.reason, err.message);
    });
    ```
  </Accordion>
</AccordionGroup>

<Note>
  Refer to [Error handling](/form-filling-sdk/guides/error-handling) for the full error code table.
</Note>

## Next steps

<Icon icon="file-lines" iconType="solid" /> [Basic usage example](/form-filling-sdk/examples/basic-usage) for a fuller sample with **`useFormFilling`**

<Icon icon="file-lines" iconType="solid" /> [Session workflow](/form-filling-sdk/guides/integration-patterns) for single-form vs multi-form behavior

<Icon icon="file-lines" iconType="solid" /> [Configuration](/form-filling-sdk/guides/configuration) for all **`<FormFilling>`** props

<Icon icon="file-lines" iconType="solid" /> [Javascript integration](/form-filling-sdk/javaScript-integration/javaScript) for plain JavaScript
