React Integration
Integrate Ozwell into your React application with a simple component wrapper around the iframe-based widget.
Installationβ
npm install @ozwell/react
# or
yarn add @ozwell/react
# or
pnpm add @ozwell/react
Quick Startβ
import { OzwellChat } from '@ozwell/react';
function App() {
return (
<div>
<h1>My App</h1>
<OzwellChat
apiKey="ozw_scoped_xxxxxxxx"
agentId="agent_xxxxxxxx"
/>
</div>
);
}
Component APIβ
<OzwellChat />β
The main chat widget component.
import { OzwellChat } from '@ozwell/react';
<OzwellChat
apiKey="ozw_scoped_xxxxxxxx"
agentId="agent_xxxxxxxx"
theme="auto"
position="bottom-right"
primaryColor="#4f46e5"
width="400px"
height="600px"
autoOpen={false}
greeting="Hello! How can I help?"
placeholder="Type a message..."
onReady={() => console.log('Ready')}
onOpen={() => console.log('Opened')}
onClose={() => console.log('Closed')}
onUserShare={(data) => console.log('User shared:', data)}
/>
Propsβ
| Prop | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Scoped API key |
agentId | string | required | Agent ID |
theme | 'light' | 'dark' | 'auto' | 'auto' | Color theme |
position | 'bottom-right' | 'bottom-left' | 'bottom-right' | Widget position |
primaryColor | string | '#4f46e5' | Accent color |
width | string | '400px' | Chat window width |
height | string | '600px' | Chat window height |
autoOpen | boolean | false | Open on mount |
greeting | string | Agent default | Initial message |
placeholder | string | 'Type a message...' | Input placeholder |
context | Record<string, unknown> | {} | Context data for agent |
onReady | () => void | β | Widget ready callback |
onOpen | () => void | β | Chat opened callback |
onClose | () => void | β | Chat closed callback |
onUserShare | (data: unknown) => void | β | User shared data callback (opt-in) |
onError | (error: OzwellError) => void | β | Error callback |
Privacy Note: There is no
onMessagecallback. Conversation content is private between the user and Ozwell. TheonUserSharecallback only fires when the user explicitly chooses to share data with your site.
Hooksβ
useOzwell()β
Access the Ozwell instance programmatically.
import { OzwellChat, useOzwell } from '@ozwell/react';
function ChatControls() {
const ozwell = useOzwell();
return (
<div>
<button onClick={() => ozwell.open()}>Open Chat</button>
<button onClick={() => ozwell.close()}>Close Chat</button>
<button onClick={() => ozwell.sendMessage('Hello!')}>
Send Hello
</button>
</div>
);
}
function App() {
return (
<OzwellChat apiKey="..." agentId="...">
<ChatControls />
</OzwellChat>
);
}
Hook APIβ
interface UseOzwellReturn {
isReady: boolean;
isOpen: boolean;
open: () => void;
close: () => void;
toggle: () => void;
sendMessage: (content: string) => void;
setContext: (context: Record<string, unknown>) => void;
}
Examplesβ
With Context Dataβ
Pass user information and page context to the agent:
import { OzwellChat } from '@ozwell/react';
import { useUser } from './auth';
import { useLocation } from 'react-router-dom';
function App() {
const user = useUser();
const location = useLocation();
return (
<OzwellChat
apiKey="ozw_scoped_xxxxxxxx"
agentId="agent_xxxxxxxx"
context={{
userId: user?.id,
email: user?.email,
page: location.pathname,
timestamp: Date.now()
}}
/>
);
}
Custom Trigger Buttonβ
Hide the default launcher and use your own button:
import { OzwellChat, useOzwell } from '@ozwell/react';
function CustomTrigger() {
const { open, isOpen } = useOzwell();
if (isOpen) return null;
return (
<button
onClick={open}
className="fixed bottom-4 right-4 bg-blue-600 text-white px-4 py-2 rounded-full"
>
π¬ Need help?
</button>
);
}
function App() {
return (
<OzwellChat
apiKey="..."
agentId="..."
// Hide default trigger
renderTrigger={() => null}
>
<CustomTrigger />
</OzwellChat>
);
}
Analytics Integrationβ
Track chat lifecycle events (not contentβthat's private):
import { OzwellChat } from '@ozwell/react';
import { analytics } from './analytics';
function App() {
return (
<OzwellChat
apiKey="ozw_scoped_xxxxxxxx"
agentId="agent_xxxxxxxx"
onOpen={() => {
analytics.track('Chat Opened');
}}
onClose={() => {
analytics.track('Chat Closed');
}}
onUserShare={(data) => {
// Only fires when user explicitly shares
analytics.track('User Shared Data', data);
}}
/>
);
}
Conditional Renderingβ
Only show chat on certain pages:
import { OzwellChat } from '@ozwell/react';
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
const showChat = !location.pathname.startsWith('/checkout');
return (
<div>
{/* App content */}
{showChat && (
<OzwellChat apiKey="..." agentId="..." />
)}
</div>
);
}
TypeScriptβ
The package includes full TypeScript definitions:
import type { OzwellChatProps, OzwellError } from '@ozwell/react';
const config: OzwellChatProps = {
apiKey: 'ozw_scoped_xxxxxxxx',
agentId: 'agent_xxxxxxxx',
theme: 'dark',
onUserShare: (data: unknown) => {
// Only fires when user explicitly shares
console.log('User shared:', data);
},
onError: (error: OzwellError) => {
console.error(error.code, error.message);
}
};
Privacy Note: There is no
Messagetype exported. Conversation content is private.
Troubleshootingβ
Widget Not Appearingβ
- Ensure the component is mounted in the DOM
- Check that
apiKeyandagentIdare valid - Look for console errors
Context Not Updatingβ
The context prop is not deeply compared. To trigger updates:
// β Won't trigger update (same object reference)
const context = { page: location.pathname };
// β
Will trigger update (new object)
const context = useMemo(
() => ({ page: location.pathname }),
[location.pathname]
);
Multiple Instancesβ
Only render one <OzwellChat /> component per page. If you need different agents on different routes, conditionally render with different props.
Next Stepsβ
- Next.js Integration β SSR considerations
- Iframe Details β Security deep-dive
- Backend API β Server-side integration