Message Handling
Learn how to handle message events, track conversations, and integrate with analytics or other systems.
The onMessagesChange Callback
InAppAI React uses controlled mode, which means you manage the message state. The onMessagesChange callback is called whenever the messages array changes.
import { useState } from 'react';
import { InAppAI, Message } from '@inappai/react';
function App() {
const [messages, setMessages] = useState<Message[]>([]);
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={(newMessages) => {
console.log('Messages updated:', newMessages);
setMessages(newMessages);
}}
/>
);
}
Tracking Message Events
Since you control the messages array, you can detect when messages are sent or received by comparing the old and new arrays:
function ChatWithTracking() {
const [messages, setMessages] = useState<Message[]>([]);
const handleMessagesChange = (newMessages: Message[]) => {
// Detect new messages by comparing lengths
if (newMessages.length > messages.length) {
const newMessage = newMessages[newMessages.length - 1];
if (newMessage.role === 'user') {
console.log('User sent:', newMessage.content);
// Track user message
analytics.track('User Message', {
length: newMessage.content.length,
});
} else if (newMessage.role === 'assistant') {
console.log('AI responded:', newMessage.content);
// Track AI response
analytics.track('AI Response', {
length: newMessage.content.length,
});
}
}
setMessages(newMessages);
};
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={handleMessagesChange}
/>
);
}
Common Patterns
Save to localStorage
const handleMessagesChange = (newMessages: Message[]) => {
setMessages(newMessages);
localStorage.setItem('messages', JSON.stringify(newMessages));
};
Save to Backend
const handleMessagesChange = async (newMessages: Message[]) => {
setMessages(newMessages);
// Debounce or save only on new messages
if (newMessages.length > messages.length) {
await api.saveMessages(conversationId, newMessages);
}
};
Auto-Generate Conversation Title
const handleMessagesChange = (newMessages: Message[]) => {
setMessages(newMessages);
// Auto-generate title from first user message
if (newMessages.length === 1 && newMessages[0].role === 'user') {
const title = newMessages[0].content.slice(0, 50);
setConversationTitle(title);
}
};
Track Response Time
function ChatWithResponseTime() {
const [messages, setMessages] = useState<Message[]>([]);
const [lastUserMessageTime, setLastUserMessageTime] = useState<number>(0);
const handleMessagesChange = (newMessages: Message[]) => {
if (newMessages.length > messages.length) {
const newMessage = newMessages[newMessages.length - 1];
if (newMessage.role === 'user') {
setLastUserMessageTime(Date.now());
} else if (newMessage.role === 'assistant' && lastUserMessageTime > 0) {
const responseTime = Date.now() - lastUserMessageTime;
console.log('Response time:', responseTime, 'ms');
analytics.track('AI Response Time', { ms: responseTime });
}
}
setMessages(newMessages);
};
return (
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={handleMessagesChange}
/>
);
}
Analytics Integration
Complete Analytics Example
import { useState, useRef } from 'react';
import { InAppAI, Message } from '@inappai/react';
function AnalyticsChat() {
const [messages, setMessages] = useState<Message[]>([]);
const [stats, setStats] = useState({
messagesSent: 0,
messagesReceived: 0,
avgResponseTime: 0,
});
const lastUserMessageTime = useRef<number>(0);
const handleMessagesChange = (newMessages: Message[]) => {
// Detect new message
if (newMessages.length > messages.length) {
const newMessage = newMessages[newMessages.length - 1];
if (newMessage.role === 'user') {
// Track user message
setStats(prev => ({
...prev,
messagesSent: prev.messagesSent + 1,
}));
lastUserMessageTime.current = Date.now();
analytics.track('User Message Sent', {
length: newMessage.content.length,
conversationLength: newMessages.length,
});
} else if (newMessage.role === 'assistant') {
// Track AI response
const responseTime = Date.now() - lastUserMessageTime.current;
setStats(prev => ({
...prev,
messagesReceived: prev.messagesReceived + 1,
avgResponseTime: Math.round(
(prev.avgResponseTime * prev.messagesReceived + responseTime) /
(prev.messagesReceived + 1)
),
}));
analytics.track('AI Response Received', {
length: newMessage.content.length,
responseTime,
});
}
}
setMessages(newMessages);
};
return (
<div>
{/* Stats Dashboard */}
<div className="stats">
<div>Messages Sent: {stats.messagesSent}</div>
<div>AI Responses: {stats.messagesReceived}</div>
<div>Avg Response Time: {stats.avgResponseTime}ms</div>
</div>
<InAppAI
agentId="your-agent-id"
messages={messages}
onMessagesChange={handleMessagesChange}
/>
</div>
);
}
Error Tracking
Since errors may occur during message handling, wrap your handler in try-catch:
const handleMessagesChange = async (newMessages: Message[]) => {
try {
setMessages(newMessages);
await api.saveMessages(conversationId, newMessages);
} catch (error) {
console.error('Failed to save messages:', error);
Sentry.captureException(error);
// Show user-friendly error message
setError('Failed to save your conversation');
}
};
Best Practices
1. Don’t Modify Messages in Handler
// Bad - can cause infinite loops
onMessagesChange={(newMessages) => {
setMessages([...newMessages, extraMessage]); // Triggers onMessagesChange again!
}}
// Good - just update state
onMessagesChange={(newMessages) => {
setMessages(newMessages);
}}
2. Debounce Backend Saves
import { useCallback } from 'react';
import debounce from 'lodash/debounce';
const saveToBackend = useCallback(
debounce((messages: Message[]) => {
api.saveMessages(conversationId, messages);
}, 1000),
[conversationId]
);
const handleMessagesChange = (newMessages: Message[]) => {
setMessages(newMessages);
saveToBackend(newMessages);
};
3. Use Refs for Timing Data
// Use refs for data that doesn't need to trigger re-renders
const lastMessageTime = useRef<number>(0);
const handleMessagesChange = (newMessages: Message[]) => {
if (newMessage.role === 'user') {
lastMessageTime.current = Date.now(); // No re-render needed
}
setMessages(newMessages);
};
4. Handle Async Operations Properly
const handleMessagesChange = async (newMessages: Message[]) => {
// Update state immediately for responsive UI
setMessages(newMessages);
// Then handle async operations
try {
await api.saveMessages(conversationId, newMessages);
} catch (error) {
console.error('Save failed:', error);
// Don't throw - the chat should continue working
}
};
Next Steps
- Context - Send app state to AI
- Persistence - Save conversations
- API Reference - Complete props documentation