DoDomain

@dodomain/react

The React bindings — a useDoDomainConnect hook (and a drop-in button) around the @dodomain/connect modal widget.

npm install @dodomain/react

React bindings for the embeddable widget. The widget's own API is imperative — showDoDomain() opens a modal iframe and returns a close handle — so this package's shape is a hook that owns that handle's lifecycle, not a pseudo-declarative modal component. It ships dual ESM + CJS builds with self-contained type declarations, requires React 18+ (peer dependency), and its only runtime dependency is @dodomain/connect itself — the widget is not bundled twice, and both packages stay free of any other runtime dependency.

import { useDoDomainConnect, MOUNT_BLOCKED } from "@dodomain/react";

function ConnectDomainCard({ token }: { token: string }) {
  // token comes from your server: sessions.create() via @dodomain/node
  const { open, isOpen } = useDoDomainConnect({
    token,
    onVerified: ({ domain }) => refetchDomains(),
    onClose: ({ state }) => {
      if (state !== "verified") keepThePromptVisible();
    },
    onError: (err) => {
      // The iframe never mounted (host CSP, network, content blocker):
      // send the user to the same session full-page instead.
      if (err.code === MOUNT_BLOCKED) location.assign(err.hostedUrl);
    },
  });

  return (
    <button onClick={open} disabled={isOpen}>
      Connect your domain
    </button>
  );
}

The session token is the only credential involved — the widget never sees your dd_sk_... key, exactly as with plain @dodomain/connect.

What the hook adds

Beyond forwarding the call to showDoDomain(), the hook carries three guarantees a hand-rolled click handler tends to get wrong:

  • Latest-props callbacks. The widget receives stable wrapper callbacks that read the current render's handlers at fire time, so a re-render while the modal is open never strands a stale closure. Inline arrow handlers are fine — no useCallback needed.
  • One modal at a time. open() while the modal is already open is a no-op, so a double-click cannot stack two backdrops.
  • Unmount cleanup. If the owning component unmounts while the modal is open, the modal is torn down through the widget's own close path — onClose still fires with the last-known state — instead of orphaning a full-screen iframe over the page. StrictMode's dev-only double-mount cycle is handled.

useDoDomainConnect(options)

options is exactly the widget's ShowDoDomainOptions — the same fields, semantics and defaults documented on the widget page:

OptionTypeNotes
tokenstring — requiredSession token (dd_sess_...) your server minted.
baseUrlstringDoDomain origin. Defaults to https://app.dodomain.io.
theme"light" | "dark"Pass the theme your page is rendering so the sheet matches it.
onVerified(detail: { domain?: string }) => voidFires when the domain verifies.
onClose(detail: DoDomainCloseDetail) => voidFires on every dismissal, with the session's last-known state.
onError(detail: DoDomainWidgetError) => voidFires when the flow fails to load or reports a session error.
loadTimeoutMsnumberLoad-handshake timeout before the embed counts as failed. Default 15000.

Returns:

FieldTypeNotes
open() => voidOpens the modal with the current render's options. No-op while open. Browser-only — call it from an event handler.
close() => voidDismisses programmatically — fires onClose with the last-known state, exactly like the widget's own handle.close().
isOpenbooleanWhether the modal is currently mounted.

The onVerified callback remains a UI cue: the signed connection.verified webhook is the source of truth, as everywhere else.

<DoDomainConnectButton />

When all you need is a trigger, the drop-in — a plain unstyled <button type="button"> wired to the hook:

import { DoDomainConnectButton } from "@dodomain/react";

<DoDomainConnectButton token={token} onVerified={() => refetchDomains()} className="btn">
  Connect your domain
</DoDomainConnectButton>;

It takes every hook option plus children (label, defaults to "Connect your domain"), className and disabled, and disables itself while the modal is open. For anything beyond a button that opens the flow, use the hook directly.

Re-exported vocabulary

MOUNT_BLOCKED and the widget's types — ShowDoDomainOptions, DoDomainWidgetError, DoDomainCloseDetail, DoDomainSessionState, DoDomainHandle — are re-exported, so a React app needs only this one import. Their semantics (origin-checked messages, the MOUNT_BLOCKED hosted-URL fallback, the close-state table, the frame-src https://app.dodomain.io CSP requirement) are documented on the widget page and apply unchanged.

On this page