Skip to content

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.

UI surfaceUser actionTalkLabs operation
Room entryOpen live-event-chatJoin by custom ID
Message listLoad and continue receiving activityGet room updates
ComposerSend a normal messageExecute a chat command
Reply buttonReply in the main flow or a threadQuote or threaded reply
Reaction buttonAdd or remove a reactionReact to a message
More menuReport a messageReport message
Background tabKeep an inactive session presentTouch session
Close or leaveStop updates and leave the roomExit room
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

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.

chat-room.js
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),
});

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.

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.

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.

Treat the event cursor as opaque:

  1. Pass the cursor returned by join into the next updates request.
  2. Replace it only with the cursor returned by that request.
  3. Keep rendered events keyed by event id so retrying a page is safe.
  4. On an authentication failure, refresh the user token before restarting.
  5. 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.

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.

  • 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.