Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

Message list UI

The message list is the scrolling part of a channel or a thread. It renders what a message paginator holds and turns the user's scrolling into paginator calls. This page covers the two lists, what they show after a jump, the separators, and how one message is put together. The props are in the MessageList and VirtualizedMessageList references.

The default message list

MessageList and VirtualizedMessageList

Both lists render the messages of the channel or thread they are in:

  • Inside a Thread, they render the thread's replies, thread.messagePaginator, with the parent message above them.
  • Otherwise they render the channel's messages, channel.messagePaginator.

So the same <MessageList /> works in both places. Each list subscribes to its paginator's state, and:

  • loads older messages when the user scrolls to the top;
  • loads newer messages when the user scrolls to the bottom of an interval that isn't the latest one;
  • marks the channel or thread read while the user is at the latest message and the page is visible. Read state, unread and receipts covers the exceptions.

How the paginator holds intervals of messages is on Message paginators.

The two lists differ in how they render:

MessageList VirtualizedMessageList
Renders Every loaded message Only the messages near the visible area, with react-virtuoso
Date separators On; withDateSeparator turns them off Off; withDateSeparator turns them on
Grouping by sender On; noGroupByUser turns it off Off; shouldGroupByUser turns it on
Typing indicator Built in Only with a TypingIndicator override
Message UI The MessageUI override The VirtualMessage override, else MessageUI

Use MessageList unless the DOM gets too large: very long histories, or channels with a lot of traffic such as livestreams. VirtualizedMessageList keeps the DOM small, and takes react-virtuoso options through additionalVirtuosoProps.

Jumps on screen

A jump moves the list to one message: a quoted message, a search result, a pinned message, the first unread message or the latest one. The paginator loads the interval of messages around the target, and the list then shows that interval:

  1. The interval replaces what was on screen, and the list scrolls the target to the middle.
  2. The target is highlighted with the str-chat__message--highlighted class. MessageList removes the highlight three seconds after the message has been on screen.
  3. If the interval isn't the latest one, the scroll-to-latest button shows, and scrolling down loads newer messages.

The jump itself is on Message paginators, and every place that starts one is on Moving between channels, threads and messages.

Three floating elements tell the user there is more to see:

  • The unread notification, UnreadMessagesNotification, sits at the top of a channel's list. It shows "5 unread" while the unread separator is above the visible area or not loaded at all. Clicking it jumps to the first unread message. Its ✕ marks the channel read. With showUnreadNotificationAlways, it also shows while the separator is below the visible area. Threads don't show it.
  • The scroll-to-latest button, ScrollToLatestMessageButton, shows while the list isn't at the latest message. Its badge counts the messages that arrived while the user was scrolled up, or in a thread, the new replies. Clicking it scrolls to the bottom, or jumps to the latest message when it isn't loaded.
  • The new message notification, NewMessageNotification, shows while the user is scrolled up and new messages arrive, and while the list shows an older interval. It reads "New Messages!", or "3 new messages" when the unread snapshot counts unread messages.

Separators

Date separators mark where a day starts. A DateSeparator goes before the first message and between messages sent on different days. While the user scrolls, a floating DateSeparator at the top shows the date of the messages there. In a thread, the parent message has a date separator of its own. formatDate changes how the date is written; Localization covers date formats.

With date separators on, the list also adds one before the first message that arrived since the user last read the channel, unless the user sent it. hideNewMessageSeparator turns that one off.

The unread separator, UnreadMessagesSeparator, marks the first unread message in a channel, with "5 unread". It renders from the paginator's unread snapshot: the unread state when the user opened the channel. Opening the channel marks it read but keeps the snapshot, so the user can still see where the new messages start. The snapshot clears when the user, at the latest message, catches up on a new one, and when the user clicks the separator's ✕, which also marks the channel read. Threads don't show it. Read state and unread counts explains the snapshot.

A message

For each message the list renders Message. Message creates the message's handlers (react, delete, flag, mute, pin, open the thread, retry a failed send) and puts them in MessageContext with the message. Then it renders the MessageUI override, by default MessageUI, with no props. A message UI reads everything from useMessageContext().

The default MessageUI is made of parts, each its own override key:

Part Override key
The sender's avatar Avatar
The text Rendered by renderText, below
Attachments, including a shared location Attachment
A quoted message QuotedMessage
Reactions MessageReactions
Status: sending, sent, delivered or read MessageStatus
The actions menu MessageActions
The timestamp and the edited mark MessageTimestamp, MessageEditedIndicator
The replies button, in a channel MessageRepliesCountButton
Pinned, reminder, translated and also-sent-in-channel marks PinIndicator, ReminderNotification, MessageTranslationIndicator, MessageAlsoSentInChannelIndicator
A deleted or blocked message MessageDeleted (no default), MessageDeletedBubble, MessageBlocked

A poll renders in the message's bubble. A system message, such as "Cleo joined the channel", renders the MessageSystem override instead of a message UI. The text of a message that Chat's isMessageAIGenerated marks as AI-generated renders with StreamedMessageText, which shows it as it streams in.

renderText

The text goes through renderText(text, mentionedUsers, options). The default renders Markdown with react-markdown, and adds mentions and emoji. Pass your own as the list's renderText prop. This one keeps the default and renders Markdown headings as bold text:

src/ChannelMessages.tsx
import type { RenderTextFunction } from "stream-chat-react";
import { MessageList, renderText } from "stream-chat-react";

const renderTextWithoutHeadings: RenderTextFunction = (
  text,
  mentionedUsers,
  options,
) =>
  renderText(text, mentionedUsers, {
    ...options,
    customMarkDownRenderers: {
      ...options?.customMarkDownRenderers,
      h1: ({ children }) => <strong>{children}</strong>,
      h2: ({ children }) => <strong>{children}</strong>,
    },
  });

export const ChannelMessages = () => (
  <MessageList renderText={renderTextWithoutHeadings} />
);

Keep the options the list passes in: they carry the mentions to render. The options are in the renderText reference.

Replacing parts

Register replacements with WithComponents. Overriding components explains how overrides apply.

Override key Replaces
MessageUI A whole message. Gets no props; reads useMessageContext().
VirtualMessage A whole message in VirtualizedMessageList
The keys in A message One part of a message
MessageSystem A system message
DateSeparator The date separators, floating one included, but not a thread parent's
UnreadMessagesSeparator The unread separator
UnreadMessagesNotification, NewMessageNotification The floating notifications
TypingIndicator The typing indicator
EmptyStateIndicator What a channel with no messages shows
LoadingIndicator The spinner above the messages: in MessageList while an older page loads, in VirtualizedMessageList while any page loads
ThreadHead, ThreadStart The parent message above a thread's replies, and the reply count under it
MessageListWrapper, MessageListItem The ul around the messages and the li around each, in MessageList

The default ThreadHead isn't exported, so a replacement for it can't render the default inside it.

This list shows each message as one compact line:

src/CompactMessages.tsx
import {
  MessageList,
  MessageText,
  MessageTimestamp,
  useMessageContext,
  WithComponents,
} from "stream-chat-react";

const CompactMessage = () => {
  const { message } = useMessageContext();

  return (
    <div className="compact-message">
      <MessageTimestamp />
      <strong>{message.user?.name ?? message.user?.id}</strong>
      <MessageText />
    </div>
  );
};

const overrides = { MessageUI: CompactMessage };

export const CompactMessages = () => (
  <WithComponents overrides={overrides}>
    <MessageList />
  </WithComponents>
);

MessageText and MessageTimestamp read the message from MessageContext too, so they need no props. The Custom message recipe builds a complete message UI.

Used in