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

BaxCloud chat system is synchronized in real-time across all participants in a room. Messages include sender information (name, userId) and timestamps. Message history is maintained locally and not persisted on the server.

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}
The 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

Typing indicators automatically stop after 3 seconds of inactivity. You don't need to manually call 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));
Consider grouping consecutive messages from the same sender to reduce visual clutter. Show the sender name and avatar only on the first message in each group.

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};
Message history is maintained locally and cleared when the client disconnects. For persistent chat history, implement your own server-side storage solution.

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]);