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

# React

> Monte o widget num app React com um hook que sobrevive ao double-invoke do StrictMode.

Ao final deste guia o fluxo de verificação abre num app React, montando uma única vez por `sdkUrl`
mesmo em desenvolvimento.

O `mount()` é imperativo de propósito, então o único lugar onde ele cabe num app React é dentro de
um efeito. É uma porta estreita, e é fácil de errar exatamente do jeito que o modo de
desenvolvimento do React foi feito para pegar: o `<StrictMode>` invoca cada efeito duas vezes, e uma
montagem que não é idempotente monta o widget duas vezes ou destrói a sessão que acabou de criar.

## Pré-requisitos

* Node.js 20 ou superior.
* Uma `sdkUrl` emitida pelo seu backend, como no [passo 6](/setup/integrate).

<Info>
  O wrapper `@legitimuz/websdk-react` — com `useLegitimuz` e `<LegitimuzWidget />` — publica no npm
  no lançamento. O hook abaixo tem a mesma forma, então trocar por um import é mudança de uma linha.
</Info>

## Monte o fluxo

<Steps>
  <Step title="Carregue o SDK">
    ```html title="index.html" theme={null}
    <script src="https://sdk.legitimuz.com/v1/websdk.js"></script>
    ```

    Antes do bundle. O script registra `window.Legitimuz`.
  </Step>

  <Step title="Declare o global">
    O build de CDN não traz tipos. Declare o que você usa:

    ```ts title="src/legitimuz.d.ts" theme={null}
    export interface WebSdkEvent {
      type: string;
      payload?: Record<string, unknown>;
    }
    export interface WebSdkCompleteResult {
      status: "submitted" | "abandoned";
    }
    export interface WebSdkCancelResult {
      sessionId?: string;
    }
    export interface WebSdkError {
      code: number;
      name: string;
      type: string;
      message: string;
      user_message: string;
      context?: Record<string, unknown>;
      doc_url?: string;
      recoverable: boolean;
    }
    export interface LegitimuzWidgetHandle {
      destroy(): void;
      readonly ready: Promise<void>;
    }
    export interface MountOptions {
      sdkUrl: string;
      target: HTMLElement;
      colorScheme?: "light" | "dark" | "auto";
      presentation?: "modal" | "inline";
      locale?: string;
      closeButton?: "left" | "right" | "hidden";
      iframeTitle?: string;
      readyTimeoutMs?: number;
      onReady?: () => void;
      onEvent?: (event: WebSdkEvent) => void;
      /** Filtro opt-in do onEvent. Ausente, tudo passa. Não afeta onComplete/onCancel/onError. */
      eventsAllowlist?: readonly string[];
      onComplete?: (result: WebSdkCompleteResult) => void;
      onCancel?: (result: WebSdkCancelResult) => void;
      onError?: (error: WebSdkError) => void;
    }

    declare global {
      interface Window {
        Legitimuz: { mount(options: MountOptions): LegitimuzWidgetHandle };
      }
    }
    ```
  </Step>

  <Step title="Escreva o hook">
    ```ts title="src/useLegitimuz.ts" theme={null}
    import { useEffect, useRef } from "react";
    import type { LegitimuzWidgetHandle, MountOptions } from "./legitimuz";

    export type UseLegitimuzOptions = Omit<MountOptions, "target">;

    export function useLegitimuz(options: UseLegitimuzOptions) {
      const containerRef = useRef<HTMLDivElement | null>(null);
      const handleRef = useRef<LegitimuzWidgetHandle | null>(null);

      useEffect(() => {
        if (!containerRef.current) return;

        handleRef.current = window.Legitimuz.mount({
          ...options,
          target: containerRef.current,
        });

        // Roda uma vez por montagem real, incluindo o primeiro passe descartado do StrictMode
        // em dev. Sem isto, a câmera do widget sobrevive ao unmount que deveria encerrá-la.
        return () => handleRef.current?.destroy();
        // sdkUrl apenas: remontar a cada mudança de opção mataria a sessão em andamento
      }, [options.sdkUrl]);

      return containerRef;
    }
    ```
  </Step>

  <Step title="Use no componente">
    ```tsx title="src/VerificationWidget.tsx" theme={null}
    import { useLegitimuz, type UseLegitimuzOptions } from "./useLegitimuz";

    export function VerificationWidget(props: UseLegitimuzOptions) {
      const containerRef = useLegitimuz(props);
      return <div ref={containerRef} style={{ height: 640 }} />;
    }
    ```

    ```tsx title="src/App.tsx" theme={null}
    import { useState } from "react";
    import { VerificationWidget } from "./VerificationWidget";
    import type { WebSdkCompleteResult, WebSdkError, WebSdkEvent } from "./legitimuz";

    const sdkUrl = import.meta.env.VITE_LEGITIMUZ_SDK_URL as string;

    export default function App() {
      const [status, setStatus] = useState("montando");
      const [log, setLog] = useState<string[]>([]);
      const append = (line: string) => setLog((entries) => [...entries, line]);

      return (
        <>
          <VerificationWidget
            sdkUrl={sdkUrl}
            onReady={() => setStatus("pronto")}
            onEvent={(event: WebSdkEvent) => append(event.type)}
            onComplete={(result: WebSdkCompleteResult) => setStatus(`fim: ${result.status}`)}
            onCancel={() => setStatus("cancelado")}
            onError={(error: WebSdkError) => setStatus(`erro ${error.code}`)}
          />
          <p>{status}</p>
          <ul>{log.map((entry, i) => <li key={i}>{entry}</li>)}</ul>
        </>
      );
    }
    ```
  </Step>

  <Step title="Configure a sdkUrl">
    ```bash title=".env" theme={null}
    VITE_LEGITIMUZ_SDK_URL=<SUA_SDK_URL>
    ```

    O `.env` fica fora do controle de versão. A `sdkUrl` carrega a credencial da verificação no
    fragmento — nunca comite um valor real.
  </Step>
</Steps>

## Confira que funcionou

A primeira tela aparece e o `status` vira `pronto`. Em desenvolvimento, com `<StrictMode>` ligado,
o widget aparece **uma vez** — se aparecerem dois iframes, o `destroy()` do cleanup não está
rodando.

## A armadilha deste framework

<Warning>
  O array de dependências do efeito é `[options.sdkUrl]`, não `[options]`. Um objeto de opções novo
  a cada render remontaria o widget a cada render, matando a sessão que o titular está no meio de
  completar.
</Warning>

## Próximo passo

<Columns cols={2}>
  <Card title="Next.js" icon="triangle" href="/frameworks/next-js">
    O mesmo hook, com o cuidado extra que o App Router exige.
  </Card>

  <Card title="Opções do mount()" icon="settings-2" href="/web-sdk/options">
    Aparência, idioma, timeout e todos os callbacks.
  </Card>
</Columns>
