ChatIQBETA
FeaturesPricingDemoBlogDocsContact
Sign inSign up
ChatIQ

Build reliable AI chatbots powered by your team’s knowledge. Secure multi-tenant architecture, instant document ingestion, and guided analytics out of the box.

Product
FeaturesPricingDemo
Resources
DocsContactCustomer Portal
Legal & Preferences
Terms of ServicePrivacy PolicySecurity
© 2025 ChatIQ. All rights reserved.Made with care in distributed workspaces worldwide.

React Integration

Integrate ChatIQ chatbots into your React application with reusable components and hooks.

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

  1. Use server-side proxy - Never expose API keys
  2. Handle loading states - Show typing indicators
  3. Maintain conversation context - Use conversation IDs
  4. Error handling - Provide user-friendly errors
  5. Accessibility - Add ARIA labels and keyboard support
  6. 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