QuackChat in Single-Page Applications
This guide covers integrating the QuackChat widget into React, Vue, Next.js, Nuxt, and Angular applications.
Key Considerations for SPAs
Single-page applications have unique requirements:
- Lifecycle management: Widget must be initialized after mount and cleaned up on unmount
- Route changes: Widget persists across route changes (usually desired)
- SSR compatibility: Script must only load on the client side
- TypeScript support: Type definitions for the global
QuackChatWidgetobject
React
Basic Integration
tsx
// components/QuackChatWidget.tsx
import { useEffect } from 'react';
interface QuackChatWidgetProps {
botId: string;
position?: 'bottom-right' | 'bottom-left';
primaryColor?: string;
}
export function QuackChatWidget({
botId,
position = 'bottom-right',
primaryColor = '#2563eb',
}: QuackChatWidgetProps) {
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://www.quackchat.app/widget/quackchat-widget.js';
script.async = true;
script.dataset.botId = botId;
script.dataset.position = position;
script.dataset.primaryColor = primaryColor;
document.body.appendChild(script);
return () => {
if ((window as any).QuackChatWidget) {
(window as any).QuackChatWidget.destroy();
}
document.body.removeChild(script);
};
}, [botId, position, primaryColor]);
return null;
}
Usage
tsx
// App.tsx
import { QuackChatWidget } from './components/QuackChatWidget';
function App() {
return (
<>
<YourAppContent />
<QuackChatWidget botId="YOUR_BOT_ID" primaryColor="#4f46e5" />
</>
);
}
Vue
Basic Integration (Vue 3 Composition API)
vue
<!-- components/QuackChatWidget.vue -->
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
const props = defineProps<{
botId: string;
position?: string;
primaryColor?: string;
}>();
let script: HTMLScriptElement | null = null;
onMounted(() => {
script = document.createElement('script');
script.src = 'https://www.quackchat.app/widget/quackchat-widget.js';
script.async = true;
script.dataset.botId = props.botId;
if (props.position) script.dataset.position = props.position;
if (props.primaryColor) script.dataset.primaryColor = props.primaryColor;
document.body.appendChild(script);
});
onUnmounted(() => {
if ((window as any).QuackChatWidget) {
(window as any).QuackChatWidget.destroy();
}
if (script) script.remove();
});
</script>
<template></template>
Next.js
App Router (Next.js 13+)
tsx
// components/QuackChatWidget.tsx
'use client';
import { useEffect } from 'react';
export function QuackChatWidget({ botId }: { botId: string }) {
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://www.quackchat.app/widget/quackchat-widget.js';
script.async = true;
script.dataset.botId = botId;
document.body.appendChild(script);
return () => {
if ((window as any).QuackChatWidget) {
(window as any).QuackChatWidget.destroy();
}
document.body.removeChild(script);
};
}, [botId]);
return null;
}
Usage in Layout
tsx
// app/layout.tsx
import { QuackChatWidget } from '@/components/QuackChatWidget';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<QuackChatWidget botId="YOUR_BOT_ID" />
</body>
</html>
);
}
Angular
Service Approach
typescript
// services/quackchat.service.ts
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class QuackChatService {
private script: HTMLScriptElement | null = null;
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
init(config: { botId: string }) {
if (!isPlatformBrowser(this.platformId)) return;
this.script = document.createElement('script');
this.script.src = 'https://www.quackchat.app/widget/quackchat-widget.js';
this.script.async = true;
this.script.dataset['botId'] = config.botId;
document.body.appendChild(this.script);
}
destroy() {
if ((window as any).QuackChatWidget) {
(window as any).QuackChatWidget.destroy();
}
if (this.script) {
document.body.removeChild(this.script);
this.script = null;
}
}
}
Troubleshooting
Widget loads multiple times
Cause: Component re-renders without cleanup.
Solution: Ensure cleanup function removes the script and calls destroy().
"QuackChatWidget is not defined"
Cause: Script hasn't loaded yet.
Solution: Check if widget exists before calling methods:
typescript
if ((window as any).QuackChatWidget) {
(window as any).QuackChatWidget.open();
}
SSR errors ("window is not defined")
Cause: Trying to access window during server-side rendering.
Solution:
- Use
useEffect(React) oronMounted(Vue) - Use
'use client'directive (Next.js App Router) - Check
isPlatformBrowser(Angular)
Memory leaks
Cause: Widget not destroyed on component unmount.
Solution: Always call destroy() in cleanup.