message.comDevelopers

Install live chat on React

Inject the message.com widget from a useEffect hook so it mounts exactly once, after the first render. Works on Vite, Create React App, Remix, TanStack Router, and any other React shell that does not have a framework-specific Script primitive.

If you are on Next.js, use the next/script component instead. See Install on Next.js.

1. Create a hook

Drop this hook into src/lib/useMessageWidget.ts. It guards against double-mounts (React Strict Mode and Fast Refresh both fire effects twice in development).

src/lib/useMessageWidget.ts
// src/lib/useMessageWidget.ts
import { useEffect } from "react";

const WORKSPACE_ID = "YOUR_WORKSPACE_ID";

export function useMessageWidget() {
  useEffect(() => {
    if (document.getElementById("message-widget-script")) return; // dedupe
    const s = document.createElement("script");
    s.id = "message-widget-script";
    s.async = true;
    s.src = "https://app.message.com/widget.js";
    s.dataset.workspace = WORKSPACE_ID;
    document.head.appendChild(s);
  }, []);
}

2. Call it from your root component

src/App.tsx
// src/App.tsx
import { useMessageWidget } from "./lib/useMessageWidget";

export default function App() {
  useMessageWidget();
  return <main>{/* your app */}</main>;
}

Calling the hook anywhere above the route tree is fine. We recommend the root component so it survives every route change.

3. Verify the install

  1. Run npm run dev (or vite, react-scripts start, whatever).
  2. Open the app in a private window.
  3. The launcher appears in the bottom-right within two seconds.
  4. Open the browser console. Confirm the [message] startup log.
  5. Send a test message. The conversation lands in app.message.com.

4. Identify the user on sign-in

After your auth flow returns a session, identify the visitor. For production, compute an HMAC userHash server-side using your workspace signing secret. See Identify logged-in users for the HMAC pattern.

IdentifyOnSignIn.tsx
// src/components/IdentifyOnSignIn.tsx
import { useEffect } from "react";

declare global {
  interface Window {
    Message?: { identify: (p: Record<string, unknown>) => void };
    MessageOnReady?: () => void;
  }
}

export function IdentifyOnSignIn({ user }: { user: { email: string; name: string; id: string; userHash: string } }) {
  useEffect(() => {
    const identify = () => window.Message?.identify({
      email: user.email,
      name: user.name,
      userId: user.id,
      userHash: user.userHash,
    });
    if (window.Message) identify();
    else window.MessageOnReady = identify;
  }, [user]);
  return null;
}

Push route changes

If you use React Router, call window.Message?.update({ pageUrl: window.location.href }) from a small useEffect wired to useLocation(). Agents will see the visitor's current page in the inbox.

Common pitfalls

  • Double-mount. Strict Mode and Fast Refresh both fire effects twice. The dedupe check by element ID prevents two bundle loads.
  • SSR frameworks. If your React app server-renders (Remix, Astro with React islands, RedwoodJS), make sure the hook only runs on the client. useEffect bodies always run client-side, so the example above is safe.
  • Workspace ID in source. Safe to commit. The workspace ID is a public identifier. Never commit the signing secret.
  • iframe sandboxes. The widget cannot mount inside a fully sandboxed iframe. Add allow-scripts allow-same-origin to the sandbox if you embed the React app that way.

Next steps