React Integration
Integrate ChatIQ chatbots into your React application using reusable components and custom hooks.
Overview
This guide covers:
- Creating a React chat component
- Using custom hooks for chat functionality
- Handling streaming responses
- Managing conversation state
Note: For Next.js applications, see the Next.js Integration Guide which includes React Server Components.
Prerequisites
- React 16.8+ (hooks support)
- An API key from your ChatIQ dashboard
- A backend proxy endpoint (recommended) or server-side API access
Step 1: Set Up Environment Variables
Create a .env file (for your backend):
CHATIQ_API_KEY=sk_live_your_api_key_here
CHATIQ_BOT_SLUG=your-bot-slug
CHATIQ_API_URL=https://chatiq.io/api
Step 2: Create a Backend Proxy
Create an API route that proxies requests to ChatIQ (keeps API key secure):
Next.js API Route Example
// app/api/chatbot/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const { message, conversation_id } = await request.json();
const response = await fetch('https://chatiq.io/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.CHATIQ_API_KEY}`,
},
body: JSON.stringify({
message,
bot_slug: process.env.CHATIQ_BOT_SLUG,
stream: true,
conversation_id: conversation_id || null,
}),
});
return new Response(response.body, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
Express.js Example
// server.js
app.post('/api/chatbot', async (req, res) => {
const { message, conversation_id } = req.body;
const response = await fetch('https://chatiq.io/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.CHATIQ_API_KEY}`,
},
body: JSON.stringify({
message,
bot_slug: process.env.CHATIQ_BOT_SLUG,
stream: true,
conversation_id: conversation_id || null,
}),
});
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
response.body.pipeTo(
new WritableStream({
write(chunk) {
res.write(chunk);
},
close() {
res.end();
},
})
);
});
Step 3: Create a Custom Hook
Create a reusable hook for chat functionality:
// hooks/useChatIQ.ts
import { useState, useCallback } from 'react';
interface Message {
role: 'user' | 'assistant';
content: string;
}
interface UseChatIQOptions {
apiUrl?: string;
}
export function useChatIQ(options: UseChatIQOptions = {}) {
const [messages, setMessages] = useState<Message[]>([]);
const [conversationId, setConversationId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const apiUrl = options.apiUrl || '/api/chatbot';
const sendMessage = useCallback(async (message: string) => {
if (!message.trim() || isLoading) return;
setIsLoading(true);
setError(null);
// Add user message immediately
setMessages((prev) => [...prev, { role: 'user', content: message }]);
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
conversation_id: conversationId,
}),
});
if (!response.ok) {
throw new Error('Failed to get response');
}
// Handle streaming response
const reader = response.body?.getReader();
const decoder = new TextDecoder();
let fullResponse = '';
let receivedConversationId: string | null = null;
if (!reader) {
throw new Error('No response body');
}
// Create assistant message placeholder
setMessages((prev) => [...prev, { role: 'assistant', content: '' }]);
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6).trim();
if (data === '[DONE]') break;
try {
const parsed = JSON.parse(data);
// Handle content chunks
if (parsed.choices?.[0]?.delta?.content) {
fullResponse += parsed.choices[0].delta.content;
setMessages((prev) => {
const updated = [...prev];
updated[updated.length - 1] = {
role: 'assistant',
content: fullResponse,
};
return updated;
});
}
// Handle conversation ID
if (parsed.conversationId) {
receivedConversationId = parsed.conversationId;
}
} catch (e) {
// Skip invalid JSON
}
}
}
}
if (receivedConversationId) {
setConversationId(receivedConversationId);
}
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
setMessages((prev) => [
...prev,
{
role: 'assistant',
content: 'Sorry, something went wrong. Please try again.',
},
]);
} finally {
setIsLoading(false);
}
}, [apiUrl, conversationId, isLoading]);
const clearMessages = useCallback(() => {
setMessages([]);
setConversationId(null);
setError(null);
}, []);
return {
messages,
sendMessage,
clearMessages,
isLoading,
error,
conversationId,
};
}
Step 4: Create a Chat Component
Create a reusable chat component:
// components/ChatWidget.tsx
import React, { useState, useRef, useEffect } from 'react';
import { useChatIQ } from '../hooks/useChatIQ';
export function ChatWidget() {
const { messages, sendMessage, isLoading } = useChatIQ();
const [input, setInput] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
sendMessage(input);
setInput('');
};
return (
<div className="chat-container">
<div className="chat-messages">
{messages.length === 0 ? (
<div className="empty-state">
Start a conversation...
</div>
) : (
messages.map((msg, idx) => (
<div key={idx} className={`message ${msg.role}`}>
{msg.content}
</div>
))
)}
{isLoading && (
<div className="message assistant typing">
Thinking...
</div>
)}
<div ref={messagesEndRef} />
</div>
<form onSubmit={handleSubmit} className="chat-input">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type your message..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading || !input.trim()}>
Send
</button>
</form>
</div>
);
}
Step 5: Use in Your App
Use the component in your React app:
// App.tsx
import { ChatWidget } from './components/ChatWidget';
function App() {
return (
<div className="App">
<h1>My App</h1>
<ChatWidget />
</div>
);
}
export default App;
Advanced: Floating Widget
Create a floating chat button:
// components/FloatingChat.tsx
import React, { useState } from 'react';
import { ChatWidget } from './ChatWidget';
export function FloatingChat() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button
className="floating-button"
onClick={() => setIsOpen(!isOpen)}
aria-label="Toggle chat"
>
💬
</button>
{isOpen && (
<div className="chat-window">
<div className="chat-header">
<h3>Chat with us</h3>
<button onClick={() => setIsOpen(false)}>×</button>
</div>
<ChatWidget />
</div>
)}
</>
);
}
Styling
Add CSS for your chat widget:
.chat-container {
display: flex;
flex-direction: column;
height: 500px;
border: 1px solid #ddd;
border-radius: 8px;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 15px;
}
.message {
margin-bottom: 10px;
padding: 8px 12px;
border-radius: 8px;
max-width: 80%;
}
.message.user {
background: #007bff;
color: white;
margin-left: auto;
text-align: right;
}
.message.assistant {
background: #f1f1f1;
color: #333;
}
.chat-input {
display: flex;
padding: 10px;
border-top: 1px solid #ddd;
}
.chat-input input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.chat-input button {
margin-left: 10px;
padding: 8px 16px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.floating-button {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
border-radius: 50%;
background: #4CAF50;
color: white;
border: none;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.chat-window {
position: fixed;
bottom: 90px;
right: 20px;
width: 350px;
height: 500px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
}
Error Handling
Enhance error handling in your hook:
// Enhanced error handling
try {
// ... API call
} catch (err) {
if (err instanceof TypeError && err.message.includes('fetch')) {
setError(new Error('Network error: Unable to connect'));
} else {
setError(err instanceof Error ? err : new Error('Unknown error'));
}
}
Best Practices
- Use server-side proxy - Never expose API keys
- Handle loading states - Show typing indicators
- Maintain conversation context - Use conversation IDs
- Error handling - Provide user-friendly errors
- Accessibility - Add ARIA labels and keyboard support
- Performance - Debounce rapid messages if needed
Next Steps
- See API Reference for complete API docs
- Check Streaming Guide for SSE details
- Explore Next.js Integration for Next.js apps
Need Help?
- Documentation: Full API Docs
- Support: support@chatiq.io