Chat System
Learn how to implement real-time text messaging with typing indicators and message history
Overview
BaxCloud provides a built-in real-time chat system for text messaging, typing indicators, and message history. The SDK handles message synchronization across all participants with low-latency delivery and automatic user identification.
Important
Real-Time Messaging
Send and receive text messages instantly with automatic synchronization
Typing Indicators
Show when participants are typing with automatic timeout
Message History
Access chat history with timestamps and sender information
Basic Chat Implementation
Send, receive messages, and access chat history
Use the chat API to send messages, listen for incoming messages, and retrieve message history.
1import { useState, useEffect } from 'react';
2import { BaxCloudClient } from '@baxcloud/react-sdk';
3
4function ChatDemo() {
5 const [client] = useState(() => new BaxCloudClient({
6 projectId: 'your-project-id',
7 apiKey: 'your-api-key',
8 }));
9
10 const [messages, setMessages] = useState<ChatMessage[]>([]);
11
12 useEffect(() => {
13 // Listen for incoming messages
14 const unsubscribe = client.onChatMessage((message) => {
15 setMessages((prev) => [...prev, message]);
16 });
17
18 // Get existing message history
19 const history = client.getChatHistory();
20 setMessages(history);
21
22 return unsubscribe;
23 }, [client]);
24
25 const sendMessage = (text: string) => {
26 client.sendChatMessage(text);
27 };
28
29 return (
30 <div>
31 <div className="messages">
32 {messages.map((msg, idx) => (
33 <div key={idx}>
34 <strong>{msg.senderName}:</strong> {msg.message}
35 </div>
36 ))}
37 </div>
38
39 <input
40 type="text"
41 onKeyPress={(e) => {
42 if (e.key === 'Enter' && e.currentTarget.value) {
43 sendMessage(e.currentTarget.value);
44 e.currentTarget.value = '';
45 }
46 }}
47 placeholder="Type a message..."
48 />
49 </div>
50 );
51}onChatMessage callback is triggered whenever a new message is received, including messages sent by the local participant. Use getChatHistory() to retrieve all messages that were sent during the current session.Typing Indicators
Show when participants are typing
Typing indicators let participants know when others are composing a message. Indicators automatically stop after 3 seconds of inactivity.
1import { useState, useEffect } from 'react';
2import { BaxCloudClient } from '@baxcloud/react-sdk';
3
4function ChatWithTyping() {
5 const [client] = useState(() => new BaxCloudClient({
6 projectId: 'your-project-id',
7 apiKey: 'your-api-key',
8 }));
9
10 const [typingUsers, setTypingUsers] = useState<string[]>([]);
11
12 useEffect(() => {
13 // Listen for typing indicator changes
14 const interval = setInterval(() => {
15 const typing = client.getTypingIndicators();
16 setTypingUsers(typing.map(u => u.name));
17 }, 500);
18
19 return () => clearInterval(interval);
20 }, [client]);
21
22 const handleInputChange = (text: string) => {
23 if (text.length > 0) {
24 client.startTyping();
25 } else {
26 client.stopTyping();
27 }
28 };
29
30 return (
31 <div>
32 {/* Chat messages */}
33
34 {/* Typing indicator */}
35 {typingUsers.length > 0 && (
36 <div className="typing-indicator">
37 {typingUsers.join(', ')} {typingUsers.length === 1 ? 'is' : 'are'} typing...
38 </div>
39 )}
40
41 <input
42 type="text"
43 onChange={(e) => handleInputChange(e.target.value)}
44 placeholder="Type a message..."
45 />
46 </div>
47 );
48}Auto-Timeout
stopTyping() unless you want to explicitly clear the indicator.Complete Chat UI Examples
Full chat components with messages list, input field, and send button
Complete chat component implementations with proper styling, message bubbles, and user avatars.
1import { useState, useEffect, useRef } from 'react';
2import { BaxCloudClient, ChatMessage } from '@baxcloud/react-sdk';
3import './ChatComponent.css';
4
5interface ChatComponentProps {
6 client: BaxCloudClient;
7 currentUserId: string;
8}
9
10function ChatComponent({ client, currentUserId }: ChatComponentProps) {
11 const [messages, setMessages] = useState<ChatMessage[]>([]);
12 const [inputText, setInputText] = useState('');
13 const [typingUsers, setTypingUsers] = useState<string[]>([]);
14 const messagesEndRef = useRef<HTMLDivElement>(null);
15
16 useEffect(() => {
17 // Listen for incoming messages
18 const unsubscribe = client.onChatMessage((message) => {
19 setMessages((prev) => [...prev, message]);
20 });
21
22 // Get existing message history
23 const history = client.getChatHistory();
24 setMessages(history);
25
26 // Poll typing indicators
27 const interval = setInterval(() => {
28 const typing = client.getTypingIndicators();
29 setTypingUsers(typing.map(u => u.name));
30 }, 500);
31
32 return () => {
33 unsubscribe();
34 clearInterval(interval);
35 };
36 }, [client]);
37
38 useEffect(() => {
39 // Auto-scroll to bottom
40 messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
41 }, [messages]);
42
43 const handleInputChange = (text: string) => {
44 setInputText(text);
45 if (text.length > 0) {
46 client.startTyping();
47 } else {
48 client.stopTyping();
49 }
50 };
51
52 const sendMessage = () => {
53 if (inputText.trim()) {
54 client.sendChatMessage(inputText);
55 setInputText('');
56 client.stopTyping();
57 }
58 };
59
60 const formatTimestamp = (timestamp: number) => {
61 const date = new Date(timestamp);
62 return date.toLocaleTimeString('en-US', {
63 hour: '2-digit',
64 minute: '2-digit'
65 });
66 };
67
68 return (
69 <div className="chat-container">
70 <div className="chat-header">
71 <h2>Chat</h2>
72 </div>
73
74 <div className="messages-container">
75 {messages.map((msg, idx) => {
76 const isOwnMessage = msg.senderId === currentUserId;
77
78 return (
79 <div
80 key={idx}
81 className={`message-wrapper ${isOwnMessage ? 'own-message' : 'other-message'}`}
82 >
83 {!isOwnMessage && (
84 <div className="avatar">
85 {msg.senderName.charAt(0).toUpperCase()}
86 </div>
87 )}
88
89 <div className="message-content">
90 {!isOwnMessage && (
91 <div className="sender-name">{msg.senderName}</div>
92 )}
93 <div className={`message-bubble ${isOwnMessage ? 'own' : 'other'}`}>
94 <div className="message-text">{msg.message}</div>
95 <div className="message-time">{formatTimestamp(msg.timestamp)}</div>
96 </div>
97 </div>
98
99 {isOwnMessage && (
100 <div className="avatar own">
101 {msg.senderName.charAt(0).toUpperCase()}
102 </div>
103 )}
104 </div>
105 );
106 })}
107 <div ref={messagesEndRef} />
108 </div>
109
110 {typingUsers.length > 0 && (
111 <div className="typing-indicator">
112 {typingUsers.join(', ')} {typingUsers.length === 1 ? 'is' : 'are'} typing...
113 </div>
114 )}
115
116 <div className="input-container">
117 <input
118 type="text"
119 value={inputText}
120 onChange={(e) => handleInputChange(e.target.value)}
121 onKeyPress={(e) => {
122 if (e.key === 'Enter') {
123 sendMessage();
124 }
125 }}
126 placeholder="Type a message..."
127 className="message-input"
128 />
129 <button onClick={sendMessage} className="send-button">
130 Send
131 </button>
132 </div>
133 </div>
134 );
135}
136
137// ChatComponent.css
138/*
139.chat-container {
140 display: flex;
141 flex-direction: column;
142 height: 600px;
143 background: #fff;
144 border-radius: 8px;
145 box-shadow: 0 2px 8px rgba(0,0,0,0.1);
146}
147
148.chat-header {
149 padding: 16px;
150 border-bottom: 1px solid #e0e0e0;
151 background: #f5f5f5;
152 border-radius: 8px 8px 0 0;
153}
154
155.messages-container {
156 flex: 1;
157 overflow-y: auto;
158 padding: 16px;
159 display: flex;
160 flex-direction: column;
161 gap: 12px;
162}
163
164.message-wrapper {
165 display: flex;
166 gap: 8px;
167 align-items: flex-end;
168}
169
170.message-wrapper.own-message {
171 flex-direction: row-reverse;
172}
173
174.avatar {
175 width: 32px;
176 height: 32px;
177 border-radius: 50%;
178 background: #3b82f6;
179 color: white;
180 display: flex;
181 align-items: center;
182 justify-content: center;
183 font-weight: bold;
184 font-size: 14px;
185 flex-shrink: 0;
186}
187
188.avatar.own {
189 background: #10b981;
190}
191
192.message-content {
193 max-width: 70%;
194}
195
196.sender-name {
197 font-size: 12px;
198 color: #666;
199 margin-bottom: 4px;
200 padding-left: 12px;
201}
202
203.message-bubble {
204 padding: 8px 12px;
205 border-radius: 12px;
206}
207
208.message-bubble.other {
209 background: #f0f0f0;
210 color: #000;
211}
212
213.message-bubble.own {
214 background: #3b82f6;
215 color: white;
216}
217
218.message-text {
219 font-size: 14px;
220 line-height: 1.4;
221}
222
223.message-time {
224 font-size: 11px;
225 opacity: 0.7;
226 margin-top: 4px;
227}
228
229.typing-indicator {
230 padding: 8px 16px;
231 font-size: 13px;
232 font-style: italic;
233 color: #666;
234}
235
236.input-container {
237 display: flex;
238 gap: 8px;
239 padding: 16px;
240 border-top: 1px solid #e0e0e0;
241}
242
243.message-input {
244 flex: 1;
245 padding: 10px 12px;
246 border: 1px solid #e0e0e0;
247 border-radius: 20px;
248 outline: none;
249 font-size: 14px;
250}
251
252.message-input:focus {
253 border-color: #3b82f6;
254}
255
256.send-button {
257 padding: 10px 20px;
258 background: #3b82f6;
259 color: white;
260 border: none;
261 border-radius: 20px;
262 cursor: pointer;
263 font-weight: 500;
264}
265
266.send-button:hover {
267 background: #2563eb;
268}
269*/
270
271export default ChatComponent;Message Formatting
Format timestamps, add user avatars, and style message bubbles
Each chat message includes a timestamp, sender name, sender ID, and the message text. Use these properties to create rich chat UIs with proper formatting.
Message Structure
1interface ChatMessage {
2 message: string; // The message text
3 senderName: string; // Name of the sender
4 senderId: string; // User ID of the sender
5 timestamp: number; // Unix timestamp in milliseconds
6}Timestamp Formatting
1// JavaScript/TypeScript
2const formatTimestamp = (timestamp: number) => {
3 const date = new Date(timestamp);
4 return date.toLocaleTimeString('en-US', {
5 hour: '2-digit',
6 minute: '2-digit'
7 });
8};
9
10// Relative time (e.g., "2 minutes ago")
11const getRelativeTime = (timestamp: number) => {
12 const diff = Date.now() - timestamp;
13 const minutes = Math.floor(diff / 60000);
14
15 if (minutes < 1) return 'Just now';
16 if (minutes < 60) return `${minutes}m ago`;
17
18 const hours = Math.floor(minutes / 60);
19 if (hours < 24) return `${hours}h ago`;
20
21 const days = Math.floor(hours / 24);
22 return `${days}d ago`;
23};User Avatar Generation
1// React example
2function UserAvatar({ name, isOwnMessage }: { name: string; isOwnMessage: boolean }) {
3 const initial = name.charAt(0).toUpperCase();
4 const backgroundColor = isOwnMessage ? '#10b981' : '#3b82f6';
5
6 return (
7 <div
8 style={{
9 width: 32,
10 height: 32,
11 borderRadius: '50%',
12 backgroundColor,
13 color: 'white',
14 display: 'flex',
15 alignItems: 'center',
16 justifyContent: 'center',
17 fontWeight: 'bold',
18 fontSize: 14,
19 }}
20 >
21 {initial}
22 </div>
23 );
24}Message Grouping by Date
1function groupMessagesByDate(messages: ChatMessage[]) {
2 const groups: { [key: string]: ChatMessage[] } = {};
3
4 messages.forEach(msg => {
5 const date = new Date(msg.timestamp);
6 const dateKey = date.toLocaleDateString('en-US', {
7 year: 'numeric',
8 month: 'long',
9 day: 'numeric'
10 });
11
12 if (!groups[dateKey]) {
13 groups[dateKey] = [];
14 }
15 groups[dateKey].push(msg);
16 });
17
18 return groups;
19}
20
21// Usage in render
22const groupedMessages = groupMessagesByDate(messages);
23Object.entries(groupedMessages).map(([date, msgs]) => (
24 <div key={date}>
25 <div className="date-divider">{date}</div>
26 {msgs.map(msg => <MessageBubble key={msg.timestamp} message={msg} />)}
27 </div>
28));Advanced Features
Clear history, message filtering, and user mentions
Implement advanced chat features like clearing message history, filtering messages, and handling user mentions.
Clear Chat History
1// Clear all messages from history
2client.clearChatHistory();
3
4// Auto-clear on disconnect
5client.onDisconnected(() => {
6 client.clearChatHistory();
7});
8
9// Clear after a specific time period
10const clearOldMessages = () => {
11 const history = client.getChatHistory();
12 const oneHourAgo = Date.now() - (60 * 60 * 1000);
13
14 // Filter and keep only recent messages
15 const recentMessages = history.filter(msg => msg.timestamp > oneHourAgo);
16
17 client.clearChatHistory();
18 // Re-add recent messages if needed
19};Message Filtering
1// Filter messages by sender
2const getMessagesBySender = (senderId: string) => {
3 const history = client.getChatHistory();
4 return history.filter(msg => msg.senderId === senderId);
5};
6
7// Search messages by text
8const searchMessages = (query: string) => {
9 const history = client.getChatHistory();
10 return history.filter(msg =>
11 msg.message.toLowerCase().includes(query.toLowerCase())
12 );
13};
14
15// Get messages in time range
16const getMessagesInRange = (startTime: number, endTime: number) => {
17 const history = client.getChatHistory();
18 return history.filter(msg =>
19 msg.timestamp >= startTime && msg.timestamp <= endTime
20 );
21};User Mentions
1// Detect mentions in message text
2const detectMentions = (text: string, participants: Participant[]) => {
3 const mentions: string[] = [];
4
5 participants.forEach(p => {
6 const mentionPattern = new RegExp(`@${p.name}\\b`, 'gi');
7 if (mentionPattern.test(text)) {
8 mentions.push(p.userId);
9 }
10 });
11
12 return mentions;
13};
14
15// Highlight mentions in UI
16const highlightMentions = (text: string, participants: Participant[]) => {
17 let highlightedText = text;
18
19 participants.forEach(p => {
20 const mentionPattern = new RegExp(`(@${p.name})\\b`, 'gi');
21 highlightedText = highlightedText.replace(
22 mentionPattern,
23 '<span class="mention">$1</span>'
24 );
25 });
26
27 return highlightedText;
28};
29
30// Send message with mention metadata
31const sendMessageWithMentions = (text: string, participants: Participant[]) => {
32 const mentions = detectMentions(text, participants);
33
34 // Send the message
35 client.sendChatMessage(text);
36
37 // Optionally notify mentioned users
38 if (mentions.length > 0) {
39 console.log('Mentioned users:', mentions);
40 // Implement notification logic here
41 }
42};Message Reactions
1// Custom implementation for message reactions
2// Note: Store reactions locally or use custom signaling
3
4interface MessageReaction {
5 messageTimestamp: number;
6 userId: string;
7 reaction: string; // emoji
8}
9
10const reactions: MessageReaction[] = [];
11
12const addReaction = (messageTimestamp: number, reaction: string, userId: string) => {
13 reactions.push({ messageTimestamp, userId, reaction });
14
15 // Broadcast reaction to other participants via custom data channel
16 // client.sendCustomData({ type: 'reaction', messageTimestamp, reaction });
17};
18
19const getReactionsForMessage = (messageTimestamp: number) => {
20 return reactions.filter(r => r.messageTimestamp === messageTimestamp);
21};
22
23// Group reactions by emoji
24const groupReactions = (messageTimestamp: number) => {
25 const messageReactions = getReactionsForMessage(messageTimestamp);
26 const grouped: { [emoji: string]: string[] } = {};
27
28 messageReactions.forEach(r => {
29 if (!grouped[r.reaction]) {
30 grouped[r.reaction] = [];
31 }
32 grouped[r.reaction].push(r.userId);
33 });
34
35 return grouped;
36};Best Practices
1. Message History is Not Persisted
BaxCloud chat messages are maintained locally and not persisted on the server. If you need persistent chat history, implement your own server-side storage.
2. Typing Indicators Auto-Stop After 3 Seconds
Typing indicators automatically timeout after 3 seconds. Call startTyping()on input change to keep the indicator active while the user is typing.
3. Messages Are Synchronized in Real-Time
Chat messages are synchronized across all participants with low latency. Messages sent by the local participant will also trigger the onChatMessage callback.
4. Handle Long Messages Gracefully
Implement character limits or text truncation for very long messages to maintain UI performance and readability.
1const MAX_MESSAGE_LENGTH = 500;
2
3const sendMessage = (text: string) => {
4 if (text.length > MAX_MESSAGE_LENGTH) {
5 alert(`Message too long. Max ${MAX_MESSAGE_LENGTH} characters.`);
6 return;
7 }
8
9 client.sendChatMessage(text);
10};5. Auto-Scroll to Latest Message
Automatically scroll to the bottom when new messages arrive, but allow users to scroll up to read history without interruption.
1const [autoScroll, setAutoScroll] = useState(true);
2const messagesEndRef = useRef<HTMLDivElement>(null);
3
4const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
5 const target = e.target as HTMLDivElement;
6 const isAtBottom = target.scrollHeight - target.scrollTop === target.clientHeight;
7 setAutoScroll(isAtBottom);
8};
9
10useEffect(() => {
11 if (autoScroll) {
12 messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
13 }
14}, [messages, autoScroll]);