Docs / SDK / Build / Use it with React and Vue
View as MarkdownUse it with React and Vue
Put the mindset-agent element in a React 19 or Vue 3 app, resume conversations, and load the script only when a user opens the chat.
After this page the drop-in element runs in your React 19 or Vue 3 app, resumes each user's conversation, and downloads only when someone opens it.
There is no wrapper package for either framework. Both render custom elements directly, so you use <mindset-agent> as a tag and call its methods through a ref. The examples use the /api/session endpoint from Put an agent on your page.
Load the script once
Every approach below needs mindset-agent.js on the page exactly once. Either add it to your index.html:
<script src="https://eu.mindset.ai/sdk/mindset-agent.js"></script>or load it from code when the chat first opens, as in load it only when it's needed. Don't do both, and don't load it from a component that can mount twice. A second copy logs a tag collision error. Never add mindset-agent-charts.js yourself; the element fetches it when it draws a chart.
React 19
React 19 passes props to custom elements and renders them like any tag. This component configures the element once, resumes a stored conversation, and reports the conversation ID and errors to your code.
import { useEffect, useRef, type HTMLAttributes, type Ref } from "react";
type MindsetAgentElement = HTMLElement & {
configure(config: {
getSession: () => Promise<unknown>;
conversationId?: string;
conversationList?: boolean;
}): void;
};
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"mindset-agent": HTMLAttributes<HTMLElement> & {
agent?: string;
ref?: Ref<HTMLElement>;
};
}
}
}
async function getSession() {
const r = await fetch("/api/session");
if (!r.ok) throw new Error("Could not start a session");
return r.json();
}
type Props = {
conversationId?: string;
onConversation: (conversationId: string) => void;
};
export function BillingAgent({ conversationId, onConversation }: Props) {
const ref = useRef<HTMLElement>(null);
const configured = useRef(false);
const onConversationRef = useRef(onConversation);
onConversationRef.current = onConversation;
useEffect(() => {
const el = ref.current as MindsetAgentElement | null;
if (!el) return;
const handleConversation = (e: Event) => {
const detail = (e as CustomEvent<{ conversationId: string }>).detail;
onConversationRef.current(detail.conversationId);
};
const handleError = (e: Event) => {
const detail = (e as CustomEvent<{ code: string; message: string }>).detail;
console.error("Mindset error:", detail.code, detail.message);
};
el.addEventListener("mindset:conversation", handleConversation);
el.addEventListener("mindset:error", handleError);
// StrictMode runs this effect twice in development. Configure once.
if (!configured.current) {
configured.current = true;
el.configure({
getSession,
conversationList: true,
...(conversationId ? { conversationId } : {}),
});
}
return () => {
el.removeEventListener("mindset:conversation", handleConversation);
el.removeEventListener("mindset:error", handleError);
};
}, []);
return <mindset-agent ref={ref} agent="billing-help" />;
}Things that catch people out in React:
- StrictMode creates two sessions without the guard. In development, React runs each effect, cleans it up and runs it again. Each
configure()call asks your backend for a session, so an unguarded effect creates two. Theconfiguredref keeps it to one, because React keeps refs across that second run. - Pass
conversationIdtoconfigure(), not as a JSX attribute. The element writes its ownconversation-idattribute after the first turn. Don't render that attribute from JSX, where a re-render could write a different value back and stop the conversation. - Keep
agentfixed. Changing theagentprop afterconfigure()stops the conversation and raisesstale-configuration. To switch agents, render a new component with akeythat changes with the agent, so React mounts a fresh element. - Listen with
addEventListener. The element's events have a colon in their names (mindset:error). Adding the listener in an effect, as above, works in every React version.
Store the ID your onConversation callback receives against the signed-in user, and pass it back as conversationId on their next visit.
Vue 3
Vue's template compiler warns about any tag it can't resolve, unless you tell it the tag is a custom element. With Vite, set that in vite.config.ts:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag === "mindset-agent",
},
},
}),
],
});Then use the element in a component, and configure it once it's mounted:
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
const props = defineProps<{ conversationId?: string }>();
const emit = defineEmits<{ conversation: [conversationId: string] }>();
type MindsetAgentElement = HTMLElement & {
configure(config: {
getSession: () => Promise<unknown>;
conversationId?: string;
conversationList?: boolean;
}): void;
};
const agentEl = ref<MindsetAgentElement | null>(null);
function handleConversation(e: Event) {
const detail = (e as CustomEvent<{ conversationId: string }>).detail;
emit("conversation", detail.conversationId);
}
function handleError(e: Event) {
const detail = (e as CustomEvent<{ code: string; message: string }>).detail;
console.error("Mindset error:", detail.code, detail.message);
}
onMounted(() => {
const el = agentEl.value;
if (!el) return;
el.addEventListener("mindset:conversation", handleConversation);
el.addEventListener("mindset:error", handleError);
el.configure({
getSession: async () => {
const r = await fetch("/api/session");
if (!r.ok) throw new Error("Could not start a session");
return r.json();
},
conversationList: true,
...(props.conversationId ? { conversationId: props.conversationId } : {}),
});
});
onBeforeUnmount(() => {
agentEl.value?.removeEventListener("mindset:conversation", handleConversation);
agentEl.value?.removeEventListener("mindset:error", handleError);
});
</script>
<template>
<mindset-agent ref="agentEl" agent="billing-help" />
</template>The same rules apply as in React: configure once, pass conversationId to configure() rather than binding the attribute, and keep agent fixed for the life of the element.
If you don't use Vite, set app.config.compilerOptions.isCustomElement instead. It only works when Vue compiles templates in the browser. With a build step, set the option in your build tool's Vue plugin.
Load it only when it is needed
The drop-in script is about 1.5 MB. If most visitors never open the chat, load it on the first open. This plain JavaScript works in any page or framework:
const SDK_URL = "https://eu.mindset.ai/sdk/mindset-agent.js";
let sdkReady;
function loadMindsetSdk() {
sdkReady ??= new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = SDK_URL;
script.onload = () => resolve(customElements.whenDefined("mindset-agent"));
script.onerror = () => {
sdkReady = undefined;
reject(new Error("The Mindset SDK could not be loaded"));
};
document.head.appendChild(script);
});
return sdkReady;
}
async function openChat() {
await loadMindsetSdk();
const agent = document.createElement("mindset-agent");
agent.setAttribute("agent", "billing-help");
document.querySelector("#chat").appendChild(agent);
agent.configure({
getSession: async () => {
const r = await fetch("/api/session");
if (!r.ok) throw new Error("Could not start a session");
return r.json();
},
});
}
document.querySelector("#open-chat").addEventListener("click", openChat, { once: true });#chat and #open-chat stand for your own container and button. Keep the script on your Mindset host: the element works out where to fetch charts and fonts from the address the script was loaded from. If your page uses a nonce-based content security policy, set script.nonce before appending it. See Content security policy.
In React or Vue, call loadMindsetSdk() before rendering the component that holds the tag, for example in the click handler that opens the panel.
The UI-less client in a bundled app
The UI-less client is an ES module on your Mindset host. Import it with a dynamic import() at runtime, and tell your bundler not to resolve the address:
const UILESS_URL = "https://eu.mindset.ai/sdk/mindset-agent-uiless.js";
// Vite: /* @vite-ignore */ webpack: /* webpackIgnore: true */
const { createAgentConversation } = await import(/* @vite-ignore */ UILESS_URL);In React, create the conversation once (for example in a ref guarded the same way as configure() above), because each createAgentConversation asks your backend for a session. The UI-less client covers the rest.
Related
- Layout and theming to size and color the element.
- The mindset-agent element for every method and event.