Chat - Core real-time
Push typing indicators, presence updates, read receipts, and conversation changes from your backend into the chat runtime in real time.
Implement subscribe() on the adapter to push backend updates into the runtime.
The runtime calls it on mount and cleans it up on unmount, keeping the subscription lifecycle fully managed.
The demo below shows real-time events flowing into the runtime:
Realtime presence and typing
Typing, presence, and read-state changes come in through adapter.subscribe().
none
Alice, MUI Agent
2
unread
This example focuses on state reactions from realtime events.
Subscription lifecycle
When ChatProvider mounts and the adapter implements subscribe(), the runtime does the following:
- Calls
subscribe({ onEvent })with a callback. - Stores the returned cleanup function.
- On unmount, calls the cleanup function to close the connection.
const adapter: ChatAdapter = {
async sendMessage(input) {
/* ... */
},
subscribe({ onEvent }) {
const ws = new WebSocket('/api/realtime');
ws.onmessage = (event) => onEvent(JSON.parse(event.data));
return () => ws.close();
},
};
Return the cleanup function directly or from a resolved promise to support synchronous or asynchronous setup.
Event types
The onEvent callback receives ChatRealtimeEvent objects, grouped into the following variants:
Conversation events
| Event type | Payload | Store effect |
|---|---|---|
conversation-added |
{ conversation } |
Adds the conversation to the store |
conversation-updated |
{ conversation } |
Replaces the conversation record |
conversation-removed |
{ conversationId } |
Removes the conversation and resets active ID if it matched |
Message events
| Event type | Payload | Store effect |
|---|---|---|
message-added |
{ message } |
Adds the message to the store |
message-updated |
{ message } |
Replaces the message record |
message-removed |
{ messageId, conversationId? } |
Removes the message from the store |
Typing events
| Event type | Payload | Store effect |
|---|---|---|
typing |
{ conversationId, userId, isTyping } |
Updates the typing map for the conversation |
Presence events
| Event type | Payload | Store effect |
|---|---|---|
presence |
{ userId, isOnline } |
Updates isOnline on matching conversation participants |
Read events
| Event type | Payload | Store effect |
|---|---|---|
read |
{ conversationId, messageId?, userId? } |
Updates the conversation's read state |
Consuming real-time state
Typing indicators
Use useChatStatus() to get the list of users currently typing:
function TypingIndicator() {
const { typingUserIds } = useChatStatus();
if (typingUserIds.length === 0) return null;
return <span>{typingUserIds.length} user(s) typing…</span>;
}
The typingUserIds selector returns user IDs for the active conversation by default.
For a specific conversation, use chatSelectors.typingUserIds with a conversation ID argument.
Presence
Presence events update the isOnline field on the ChatUser objects inside each conversation's participants.
Call useConversation(id) or useConversations() to read participant presence.
Read state
Read events update the readState and unreadCount fields on ChatConversation.
Use useConversation(id) to reflect read status in the UI.
Dispatching events from the backend
Each event is a plain object with a type field.
Here are the full shapes:
// Conversation events
{ type: 'conversation-added', conversation: ChatConversation }
{ type: 'conversation-updated', conversation: ChatConversation }
{ type: 'conversation-removed', conversationId: string }
// Message events
{ type: 'message-added', message: ChatMessage }
{ type: 'message-updated', message: ChatMessage }
{ type: 'message-removed', messageId: string, conversationId?: string }
// Typing
{ type: 'typing', conversationId: string, userId: string, isTyping: boolean }
// Presence
{ type: 'presence', userId: string, isOnline: boolean }
// Read
{ type: 'read', conversationId: string, messageId?: string, userId?: string }
See also
- See Adapters for the full adapter interface, including
subscribe(). - See Hooks for
useChatStatus()and the typing and presence consumption pattern. - See Real-time example for a demo covering subscriptions, typing, and presence.
- See Real-time thread sync for add, update, and remove events.