message.comDevelopers

React components SDK Planned

The React package wraps the message.com widget into idiomatic components and a hook. Use <MessageWidget> for the drop-in launcher, or useMessage for programmatic control. SSR-safe, Next.js compatible, fully typed.

Q2 2026 on the roadmap. Until then you can use the raw <script> snippet inside your app/layout.tsx or via the Next.js Script component without losing anything material.

Installation

The package is React 18 and 19 compatible and ships ESM only. Tree-shaking removes the parts of the surface you do not import.

Terminal
npm install @message-com/react
# or
pnpm add @message-com/react
# or
yarn add @message-com/react

The MessageWidget component

The simplest case: drop the component near the root of your app. It mounts the launcher, manages its own lifecycle, and cleans up on unmount.

App.tsx
import { MessageWidget } from "@message-com/react";

export default function App() {
  return (
    <>
      <YourApp />
      <MessageWidget workspaceId="abc123" />
    </>
  );
}

Props

  • workspaceId (required). Your workspace ID, also called the public site identifier.
  • color. Override the workspace primary colour.
  • position. "right" (default) or "left".
  • bottomSpacing. Pixels from the bottom edge.
  • defaultOpen. If true, opens the panel on mount.
  • noLauncher. Renders without the launcher; pair with useMessage.

The useMessage hook

For fine-grained control, use the hook. It exposes a typed API for opening, closing, identifying, sending, and subscribing to messages.

UpgradePrompt.tsx
import { useMessage } from "@message-com/react";

function UpgradePrompt() {
  const { open, identify, isReady } = useMessage();

  function startSalesChat(user) {
    identify({ email: user.email, userId: user.id });
    open();
  }

  return (
    <button disabled={!isReady} onClick={() => startSalesChat(currentUser)}>
      Talk to sales
    </button>
  );
}

Return shape

  • open(), close(), toggle().
  • identify(visitor), update(attrs), signout().
  • send(body), messages, status.
  • isReady. true once the bundle has finished loading.
  • on(event, handler). Subscribe to widget events.

Next.js

The component is SSR-safe; it renders nothing on the server and hydrates the launcher on the client. Drop it in the root layout.

layout.tsx
// app/layout.tsx, Next.js App Router
import { MessageWidget } from "@message-com/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <MessageWidget workspaceId={process.env.NEXT_PUBLIC_MESSAGE_WORKSPACE!} />
      </body>
    </html>
  );
}

Headless chat surface

If you want to render your own UI (in-product help drawer, full-page support centre, embedded chat in a sidebar), set noLauncher and drive the conversation through the hook. The hook exposes the raw message stream.

HeadlessChat.tsx
import { useMessage } from "@message-com/react";

function HeadlessChat() {
  const { messages, send, status } = useMessage();

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id} className={m.kind}>{m.body}</div>
      ))}
      <form onSubmit={(e) => { e.preventDefault(); send(input); }}>
        <input value={input} onChange={(e) => setInput(e.target.value)} />
        <button disabled={status !== "connected"}>Send</button>
      </form>
    </div>
  );
}

React StrictMode

The component is StrictMode-safe; the underlying widget bundle is initialised exactly once across the double-render. No useRef tricks required.

Common pitfalls

  • Do not render <MessageWidget> per-route. Put it once at the layout level; otherwise it remounts on every navigation.
  • Do not pass dynamic workspaceId after first mount. Changing it forces a full reinitialisation.
  • SSR identify must move to useEffect. Calling identify at module scope will throw on the server.