React integration guide
This guide covers patterns for integrating the Univerx Client SDK into React applications, including a reusable custom hook and Next.js-specific considerations.
Basic integration
Section titled “Basic integration”Create the SDK instance inside useEffect so it only runs in the browser. Always call widget.destroy() in the cleanup function to prevent memory leaks and stale connections.
import { WidgetClient } from "@univerx/client-sdk";import { useEffect, useRef } from "react";
export function SupportWidget() { const widgetRef = useRef<WidgetClient | null>(null);
useEffect(() => { const widget = new WidgetClient({ widgetKey: "your-widget-key", onAgentAssigned: (agent) => console.log(`Agent: ${agent.name}`), onCallEnded: () => widgetRef.current?.destroy(), });
widget.init(); widgetRef.current = widget;
return () => { widgetRef.current?.destroy(); }; }, []);
// The SDK renders its own UI — this component is a lifecycle wrapper return null;}Custom hook
Section titled “Custom hook”Extract the widget logic into a reusable hook to keep components clean and expose widget state to your UI:
import { WidgetClient, WidgetConfig, WidgetState } from "@univerx/client-sdk";import { useEffect, useRef, useState } from "react";
export function useWidget(config: WidgetConfig) { const widgetRef = useRef<WidgetClient | null>(null); const [state, setState] = useState<WidgetState>("idle"); const [queuePosition, setQueuePosition] = useState<number | null>(null);
useEffect(() => { const widget = new WidgetClient({ ...config, onReady: () => setState("ready"), onCallStarted: () => setState("in_call"), onCallEnded: () => setState("ended"), });
widget.on("queue:update", (position) => setQueuePosition(position)); widget.on("queue:left", () => setQueuePosition(null));
widget.init(); widgetRef.current = widget;
return () => { widget.destroy(); widgetRef.current = null; }; }, []); // eslint-disable-line react-hooks/exhaustive-deps
return { widget: widgetRef.current, state, queuePosition, };}Usage in a component:
export function SupportButton() { const { widget, state, queuePosition } = useWidget({ widgetKey: "your-widget-key", });
const handleClick = async () => { if (state === "ready") { await widget?.joinQueue({ terms: true }); } };
return ( <button onClick={handleClick} disabled={state !== "ready"}> {state === "queued" ? `Position ${queuePosition}` : "Get support"} </button> );}Cobrowsing consent
Section titled “Cobrowsing consent”When an agent requests cobrowsing, the SDK emits cobrowse:consent:required with handler functions. Render your own consent UI and call the appropriate handler:
useEffect(() => { const widget = new WidgetClient({ widgetKey: "your-widget-key" });
widget.on("cobrowse:consent:required", ({ accept, decline }) => { // Show your consent modal, then call accept() or decline() setConsentHandlers({ accept, decline }); setShowConsentModal(true); });
widget.init(); widgetRef.current = widget;
return () => widget.destroy();}, []);Next.js
Section titled “Next.js”Client component directive
Section titled “Client component directive”Add "use client" at the top of any file that imports or instantiates the SDK:
"use client";
import { WidgetClient } from "@univerx/client-sdk";Environment variables
Section titled “Environment variables”In Next.js, environment variables accessed on the client must use the NEXT_PUBLIC_ prefix:
NEXT_PUBLIC_UNIVERX_WIDGET_KEY=your-widget-key-hereconst widget = new WidgetClient({ widgetKey: process.env.NEXT_PUBLIC_UNIVERX_WIDGET_KEY!,});App Router layout integration
Section titled “App Router layout integration”To render the widget on every page, add it to your root layout. Because SupportWidget is a client component, the layout itself can remain a server component:
import { SupportWidget } from "@/components/SupportWidget";
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> {children} <SupportWidget /> </body> </html> );}Pages Router
Section titled “Pages Router”For the Pages Router, add the widget to _app.tsx:
import type { AppProps } from "next/app";import { SupportWidget } from "@/components/SupportWidget";
export default function App({ Component, pageProps }: AppProps) { return ( <> <Component {...pageProps} /> <SupportWidget /> </> );}