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
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
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
- Run
npm run dev(orvite,react-scripts start, whatever). - Open the app in a private window.
- The launcher appears in the bottom-right within two seconds.
- Open the browser console. Confirm the
[message]startup log. - 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.
// 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.
useEffectbodies 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-originto the sandbox if you embed the React app that way.