Build a complete group chat
Use this when your product needs a room screen—not only a feed of updates. The finished experience has initial messages, a composer, per-message actions, reconnect behavior, session presence, and a clean exit.
What you implement: one room view, one updates loop, and handlers for the controls you expose. The JavaScript SDK manages the current room, event cursor, polling, and session keep-alive.
Interaction map
Section titled “Interaction map”| UI surface | User action | TalkLabs operation |
|---|---|---|
| Room entry | Open live-event-chat | Join by custom ID |
| Message list | Load and continue receiving activity | Get room updates |
| Composer | Send a normal message | Execute a chat command |
| Reply button | Reply in the main flow or a thread | Quote or threaded reply |
| Reaction button | Add or remove a reaction | React to a message |
| More menu | Report a message | Report message |
| Background tab | Keep an inactive session present | Touch session |
| Close or leave | Stop updates and leave the room | Exit room |
Request sequence
Section titled “Request sequence”sequenceDiagram
actor U as User
participant UI as Room UI
participant SDK as TalkLabs SDK
participant API as TalkLabs API
U->>UI: Open live-event-chat
UI->>SDK: joinRoomByCustomId(customId)
SDK->>API: Join by custom ID
API-->>SDK: Room ID + initial events + cursor
SDK-->>UI: Render initial events
SDK->>API: Poll updates with cursor
U->>UI: Send message
UI->>SDK: executeChatCommand(text)
SDK->>API: Publish message
API-->>SDK: Published event
U->>UI: Quote, thread, react, or report
UI->>SDK: Message action
SDK->>API: Action operation
API-->>SDK: Result
SDK->>API: Continue updates
API-->>UI: Messages and state changes
U->>UI: Leave room
UI->>SDK: exitRoom()
SDK->>API: Exit
1. Configure the client and event reducer
Section titled “1. Configure the client and event reducer”Keep one event collection keyed by the TalkLabs event id. New messages append;
replace and remove events update an existing row; reactions update the message
metadata rather than creating a second visible message.
import { ChatClient } from 'sportstalk-sdk';
const client = ChatClient.init({ appId: APP_ID });client.setUserToken(userToken);client.setUser({ userid: 'u-8842', handle: 'dave_m', displayname: 'Dave M.' });
client.setEventHandlers({ onChatEvent: (event) => roomStore.apply(event), onReplace: (event) => roomStore.apply(event), onRemove: (event) => roomStore.apply(event), onReaction: (event) => roomStore.apply(event), onPurgeEvent: (event) => roomStore.apply(event), onNetworkError: (error) => connectionStore.retry(error),});2. Join by your custom ID
Section titled “2. Join by your custom ID”Joining returns the room object and initial event state. The SDK records the returned TalkLabs room ID and event cursor for later calls.
const joined = await client.joinRoomByCustomId('live-event-chat');
roomStore.setRoom(joined.room);client.startListeningToEventUpdates();If you already store the TalkLabs room ID, use joinRoom(roomId) instead. Do
not pass a custom ID to the room-ID operation.
3. Connect the composer
Section titled “3. Connect the composer”Plain text is a normal chat message. The same command operation can publish configured action, administrator, or custom events when you intentionally use those forms.
async function sendMessage(text) { const message = text.trim(); if (!message) return;
await client.executeChatCommand(message);}Disable duplicate submits while the request is in flight. The successful
response contains the published event, and the same event arrives through the
updates stream; de-duplicate by event id.
4. Add contextual actions
Section titled “4. Add contextual actions”const messageActions = { quote: (event, text) => client.sendQuotedReply(text, event.id), thread: (event, text) => client.sendThreadedReply(text, event.id), like: (event) => client.reactToEvent('like', event.id),};Use a quoted reply when the response should remain in the main room flow. Use a threaded reply when your UI has a thread panel or reply count. See Add message actions for the complete row behavior.
5. Make reconnects deterministic
Section titled “5. Make reconnects deterministic”Treat the event cursor as opaque:
- Pass the cursor returned by join into the next updates request.
- Replace it only with the cursor returned by that request.
- Keep rendered events keyed by event
idso retrying a page is safe. - On an authentication failure, refresh the user token before restarting.
- On a terminal room error, stop polling and show an explicit reconnect action.
The SDK owns this cursor while its listener is running. If you use REST directly, follow Listen for events.
6. Exit when the room is no longer active
Section titled “6. Exit when the room is no longer active”async function closeChatRoom() { await client.exitRoom();}
router.onBeforeLeave(closeChatRoom);exitRoom() stops the SDK update and keep-alive intervals before it exits the
room. Wire the cleanup to your framework’s route or component lifecycle. A
browser tab closing is best-effort, so server-side session expiry remains the
fallback.
Production checklist
Section titled “Production checklist”- Render an explicit loading, empty, reconnecting, and unavailable state.
- Keep the composer disabled until join succeeds.
- Key messages by TalkLabs event
id, not array position. - Preserve your custom room ID separately from the returned TalkLabs room ID.
- Expose report controls without placing moderator credentials in the client.
- Stop the listener and exit when the room view unmounts.
- Announce incoming messages accessibly without moving keyboard focus.