React Integration
React integration guide with basic (full-page and modal) and advanced widget methods for Sophi.
This page is organized in two levels:
- Basic setup: copy-paste patterns that customers can run with minimal edits
- Advanced setup: optional runtime controls (
sendToggleHistory,sendProductReply,triggerProductReply)
For GTM script loading and script-load failures, use SDK GTM troubleshooting.
Required in every integration
No matter which UI pattern you use (full page or modal), implement all three:
- Handle
add_to_cart - Handle
send_to_checkout - Call
widget.updateUserCart(cart)when cart changes
Basic setup
1) Access object from window
If you load SDK via GTM or script tag:
const SophiWidgetClass = window.SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass) {
throw new Error("Sophi SDK not loaded yet");
}2) Initialize widget
const widget = new SophiWidgetClass();
await widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
environment: "production",
});3) Full-page minimal example
import { useEffect, useRef } from "react";
declare global {
interface Window {
SophiWebSDK?: { SophiWidget: new () => any };
}
}
type Cart = {
cartId: string;
items: Array<{ productId: string; variantId?: string; quantity: number }>;
};
export function SophiFullPage({ cart }: { cart: Cart }) {
const widgetRef = useRef<any | null>(null);
useEffect(() => {
const SophiWidgetClass = window.SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass) return;
const widget = new SophiWidgetClass();
widgetRef.current = widget;
(async () => {
await widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
environment: "production",
width: "100%",
height: "100%",
});
widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => {
for (const p of data.products) {
await addToCartInYourApp(p.productId, p.variantId);
}
});
widget.on("send_to_checkout", () => {
window.location.href = "/checkout";
});
widget.updateUserCart({
cartId: cart.cartId,
items: cart.items,
});
})();
return () => {
widgetRef.current?.destroy?.();
widgetRef.current = null;
};
}, [cart.cartId, cart.items]);
useEffect(() => {
if (!widgetRef.current) return;
widgetRef.current.updateUserCart({
cartId: cart.cartId,
items: cart.items,
});
}, [cart.cartId, cart.items]);
return <div id="sophi-widget-container" style={{ width: "100%", height: "100vh" }} />;
}4) Modal minimal example
import { useEffect, useRef, useState } from "react";
export function SophiModal({ cart }: { cart: { cartId: string; items: any[] } }) {
const [open, setOpen] = useState(false);
const widgetRef = useRef<any | null>(null);
useEffect(() => {
if (!open) return;
const SophiWidgetClass = window.SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass) return;
if (widgetRef.current) return;
const widget = new SophiWidgetClass();
widgetRef.current = widget;
(async () => {
await widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-modal-container",
environment: "production",
width: "100%",
height: "100%",
});
widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => {
for (const p of data.products) {
await addToCartInYourApp(p.productId, p.variantId);
}
});
widget.on("send_to_checkout", () => {
setOpen(false);
window.location.href = "/checkout";
});
widget.updateUserCart({
cartId: cart.cartId,
items: cart.items,
});
})();
return () => {
widgetRef.current?.destroy?.();
widgetRef.current = null;
};
}, [open, cart.cartId, cart.items]);
useEffect(() => {
if (!widgetRef.current) return;
widgetRef.current.updateUserCart({
cartId: cart.cartId,
items: cart.items,
});
}, [cart.cartId, cart.items]);
return (
<>
<button onClick={() => setOpen(true)}>Open assistant</button>
{open && (
<div role="dialog" aria-modal="true">
<button onClick={() => setOpen(false)}>Close</button>
<div id="sophi-widget-modal-container" style={{ width: "100%", height: "70vh" }} />
</div>
)}
</>
);
}5) Required events and cart sync wiring
widget.on("add_to_cart", async (data) => {
for (const product of data.products) {
await addToCartInYourApp(product.productId, product.variantId);
}
});
widget.on("send_to_checkout", () => {
window.location.href = "/checkout";
});
widget.updateUserCart({
cartId: cartId,
items: cartItems,
});Advanced setup
Call advanced methods only after ready event or widget.isReady():
widget.on("ready", () => {
widget.sendToggleHistory();
widget.triggerProductReply(product);
widget.sendProductReply("Does this match my style?", product);
});sendToggleHistory: open/close history paneltriggerProductReply: prefill product reply contextsendProductReply: send a typed product-context question
Consent-aware (pre-consent memory-only mode)
To make the assistant available before the visitor accepts cookies,
initialize with storageConsent: false and upgrade in place when consent is
granted. See Cookie Consent Management for the full
concept.
The key points in React:
- Mount the widget once and pass the initial consent value via a ref so a later consent change does not destroy / re-init the widget.
- Watch the consent value and call
widget.setStorageConsent(true)when it flips to granted.
import { useEffect, useRef } from "react";
export function SophiConsentAware({ hasConsent }: { hasConsent: boolean }) {
const widgetRef = useRef<any | null>(null);
// Read consent inside the init effect without re-running it on change.
const hasConsentRef = useRef(hasConsent);
useEffect(() => {
hasConsentRef.current = hasConsent;
}, [hasConsent]);
// Mount the widget once — memory-only until consent.
useEffect(() => {
const SophiWidgetClass = window.SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass || widgetRef.current) return;
const widget = new SophiWidgetClass();
widgetRef.current = widget;
void widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
environment: "production",
width: "100%",
height: "100%",
storageConsent: hasConsentRef.current, // false → memory-only
});
return () => {
widgetRef.current?.destroy?.();
widgetRef.current = null;
};
}, []);
// Upgrade the live widget in place when consent is granted.
useEffect(() => {
if (hasConsent) {
void widgetRef.current?.setStorageConsent?.(true);
}
}, [hasConsent]);
return <div id="sophi-widget-container" style={{ width: "100%", height: "100vh" }} />;
}Security
- Do not hardcode real keys in source code examples
- Keep
YOUR_SOPHI_API_KEYas placeholder in docs - Keep private values out of GTM/static code