const { messages } = useChannelStateContext();
VirtualizedMessageList
The VirtualizedMessageList
component renders a scrollable list of messages. It differs from the standard MessageList
in that it handles UI virtualization by default. Virtualization is a technique used to emulate large lists of elements by
rendering as few items as possible to maintain performance and decrease the load on the DOM, while preserving the user
experience. These qualities make the VirtualizedMessageList
ideal for livestream use cases, where a single channel may
have thousands of currently active users.
Similar to the MessageList
, the UI for each individual message is rendered conditionally based on its message.type
value.
The list renders date separators, message reactions, new message notifications, system messages, deleted messages, and standard messages
containing text and/or attachments.
By default, the VirtualizedMessageList
loads the most recent 25 messages held in the channel.state
. More messages are fetched
from the Stream Chat API and loaded into the DOM on scrolling up the list. The currently loaded messages are held in
the ChannelStateContext
and can be referenced with our custom hook.
The VirtualizedMessageList
has no required props and by default pulls overridable data from the various contexts established
in the Channel
component. Customization of the messages rendered within the list is handled by
the Message UI component.
Basic Usage
As a context consumer, the VirtualizedMessageList
component must be rendered as a child of the Channel
component. It can be
rendered with or without a provided messages
prop. Providing your own messages
value will override the default
value drawn from the ChannelStateContext
.
Example 1 - without messages
prop
<Chat client={client}>
<ChannelList />
<Channel>
<VirtualizedMessageList />
<MessageInput />
</Channel>
</Chat>
Example 2 - with messages
prop
const customMessages = [
// array of messages
];
<Chat client={client}>
<ChannelList />
<Channel>
<VirtualizedMessageList messages={customMessages} />
<MessageInput />
</Channel>
</Chat>;
Scroll Behavior Optimization
By default, the virtualized message list uses the latest message (which gets rendered first) in the list as the default item size.
This can lead to inaccurate scroll thumb size or scroll jumps if the last item is unusually tall (for example, an attachment).
To improve this behavior, set the defaultItemHeight
property to a value close (or equal to) the height of the usual messages.
<VirtualizedMessageList messages={customMessages} defaultItemHeight={76} />
Message grouping
The VirtualizedMessageList
internally creates a mapping of message id to a style group. There are 4 style groups - 'middle' | 'top' | 'bottom' | 'single'
. Later these group style tags are incorporated into the class of the <li/>
element that wraps the Message
component. This allows us to style messages by their position in the message group. An example of such class is str-chat__li str-chat__li--bottom
.
Props
additionalMessageInputProps
Additional props to be passed to the MessageInput
component, available props. It is rendered when editing a message.
Type |
---|
object |
additionalVirtuosoProps
Additional props to be passed the underlying react-virtuoso
virtualized list dependency.
Type |
---|
object |
closeReactionSelectorOnClick
If true, picking a reaction from the ReactionSelector
component will close the selector.
Type | Default |
---|---|
boolean | false |
customMessageRenderer
Custom message render function, overrides the default messageRenderer
function defined in the component.
Type |
---|
( messages: StreamMessage[], index: number ) => React.ReactElement |
defaultItemHeight
If set, the default item height is used for the calculation of the total list height. Use if you expect messages with a lot of height variance.
Type |
---|
number |
disableDateSeparator
If true, disables the injection of date separator UI components.
Type | Default |
---|---|
boolean | true |
groupStyles
Callback function to set group styles for each message.
Type |
---|
(message: StreamMessage, previousMessage: StreamMessage, nextMessage: StreamMessage, noGroupByUser: boolean, maxTimeBetweenGroupedMessages?: number) => GroupStyle |
hasMore
Whether the list has more items to load.
Type | Default |
---|---|
boolean | ChannelStateContextValue[‘hasMore’] |
hideDeletedMessages
If true, removes the MessageDeleted
components from the list.
Type | Default |
---|---|
boolean | false |
hideNewMessageSeparator
If true, hides the DateSeparator
component that renders when new messages are received in a channel that’s watched but not active.
Type | Default |
---|---|
boolean | false |
loadingMore
Whether the list is currently loading more items.
Type | Default |
---|---|
boolean | ChannelStateContextValue[‘loadingMore’] |
loadMore
Function called when more messages are to be loaded, provide your own function to override the handler stored in context.
Type | Default |
---|---|
function | ChannelActionContextValue[‘loadMore’] |
maxTimeBetweenGroupedMessages
Maximum time in milliseconds that should occur between messages to still consider them grouped together.
Type |
---|
number |
Message
Custom UI component to display an individual message.
Type | Default |
---|---|
component | MessageSimple |
messageLimit
The limit to use when paginating messages (the page size).
After mounting, the VirtualizedMessageList
component checks if the list is completely filled with messages. If there is some space left in the list, VirtualizedMessageList
will load the next page of messages, but it will do so only once. This means that if your messageLimit
is too low, or if your viewport is very large, the list will not be completely filled. Set the limit with this in mind.
Type | Default |
---|---|
number | 100 |
messages
The messages to render in the list, provide your own array to override the data stored in context.
Type | Default |
---|---|
array | ChannelStateContextValue[‘messages’] |
openThread
Custom handler invoked when the button in the Message
component that opens Thread
component is clicked. To be able to define custom logic to openThread
, we need to have a wrapper around VirtualizedMessageList
component and reach out to ChannelActionContext
for the default openThread
function.
import { useCallback } from "react";
import {
VirtualizedMessageList,
useChannelActionContext,
} from "stream-chat-react";
import type { StreamMessage } from "stream-chat-react";
import type { StreamChatGenerics } from "./types";
const MessageListWrapper = () => {
const { openThread: contextOpenThread } =
useChannelActionContext<StreamChatGenerics>();
const openThread = useCallback(
(
message: StreamMessage<StreamChatGenerics>,
event?: React.BaseSyntheticEvent,
) => {
// custom logic
contextOpenThread(message, event);
},
[contextOpenThread],
);
return <VirtualizedMessageList openThread={openThread} />;
};
Type | Default |
---|---|
(message: StreamMessage, event?: React.BaseSyntheticEvent) => void | ChannelActionContextValue[‘openThread’] |
overscan
The amount of extra content the list should render in addition to what’s necessary to fill in the viewport.
Type | Default |
---|---|
number | 0 |
returnAllReadData
Keep track of read receipts for each message sent by the user. When disabled, only the last own message delivery / read status is rendered.
Type | Default |
---|---|
boolean | false |
reviewProcessedMessage
Allows to review changes introduced to messages array on per message basis (for example date separator injection before a message). The array returned from the function is appended to the array of messages that are later rendered into React elements in the VirtualizedMessageList
.
The function expects a single parameter, which is an object containing the following attributes:
changes
- array of messages representing the changes applied around a given processed messagecontext
- configuration params and information forwarded fromprocessMessages
index
- index of the processed message in the original messages arraymessages
- array of messages retrieved from the back-endprocessedMessages
- newly built array of messages to be later rendered
The context
has the following parameters:
userId
- the connected user ID;enableDateSeparator
- flag determining whether the date separators will be injected Enable date separatorhideDeletedMessages
- flag determining whether deleted messages would be filtered out during the processinghideNewMessageSeparator
- disables date separator display for unread incoming messageslastRead
: Date when the channel has been last read. Sets the threshold after everything is considered unread
The example below demonstrates how the custom logic can decide, whether deleted messages should be rendered on a given date. In this example, the deleted messages neither the date separator would be rendered if all the messages on a given date are deleted.
const getMsgDate = (msg) =>
(msg &&
msg.created_at &&
isDate(msg.created_at) &&
msg.created_at.toDateString()) ||
"";
const dateSeparatorFilter = (msg) => msg.customType !== "message.date";
const msgIsDeleted = (msg) => msg.type === "deleted";
const reviewProcessedMessage = ({
changes,
index,
messages,
processedMessages,
}) => {
const changesWithoutSeparator = changes.filter(dateSeparatorFilter);
const dateSeparatorInjected =
changesWithoutSeparator.length !== changes.length;
const previousProcessedMessage =
processedMessages[processedMessages.length - 1];
const processedMessage = messages[index];
const processedMessageDate = getMsgDate(processedMessage);
if (dateSeparatorInjected) {
if (!processedMessageDate) return changes;
const followingMessages = messages.slice(index + 1);
let allFollowingMessagesOnDateDeleted = false;
for (const followingMsg of followingMessages) {
const followingMsgDate = getMsgDate(followingMsg);
if (followingMsgDate !== processedMessageDate) break;
allFollowingMessagesOnDateDeleted = followingMsg.type === "deleted";
}
return allFollowingMessagesOnDateDeleted ? [] : changes;
} else if (
msgIsDeleted(processedMessage) &&
getMsgDate(previousProcessedMessage) !== getMsgDate(processedMessage)
) {
return [];
} else {
return changes;
}
};
scrollSeekPlaceHolder
Custom data passed to the list that determines when message placeholders should be shown during fast scrolling.
Type |
---|
object |
scrollToLatestMessageOnFocus
If true, the list will scroll to the latest message when the window regains focus.
Type | Default |
---|---|
boolean | false |
shouldGroupByUser
If true, group messages belonging to the same user, otherwise show each message individually.
Type | Default |
---|---|
boolean | false |
separateGiphyPreview
If true, the Giphy preview will render as a separate component above the MessageInput
, rather than inline with the other messages in the list.
Type | Default |
---|---|
boolean | false |
showUnreadNotificationAlways
The floating notification informing about unread messages will be shown when the UnreadMessagesSeparator
is not visible. The default is false, that means the notification
is shown only when viewing unread messages.
Type | Default |
---|---|
boolean | false |
stickToBottomScrollBehavior
The scroll-to behavior when new messages appear. Use 'smooth'
for regular chat channels and 'auto'
(which results in instant scroll to bottom) if you expect high throughput.
Type | Default |
---|---|
’smooth’ | ‘auto' | 'smooth’ |
reactionDetailsSort
Sort options to provide to a reactions query. Affects the order of reacted users in the default reactions modal.
Type | Default |
---|---|
{ created_at: number } | reverse chronological order |
sortReactions
Comparator function to sort reactions. Should have the same signature as an array’s sort
method.
Type | Default |
---|---|
(this: ReactionSummary, that: ReactionSummary) => number | chronological order |
threadList
If true, indicates that the current VirtualizedMessageList
component is part of a Thread
.
Type | Default |
---|---|
boolean | false |
- Basic Usage
- Scroll Behavior Optimization
- Message grouping
- Props
- additionalMessageInputProps
- additionalVirtuosoProps
- closeReactionSelectorOnClick
- customMessageRenderer
- defaultItemHeight
- disableDateSeparator
- groupStyles
- hasMore
- hideDeletedMessages
- hideNewMessageSeparator
- loadingMore
- loadMore
- maxTimeBetweenGroupedMessages
- Message
- messageLimit
- messages
- openThread
- overscan
- returnAllReadData
- reviewProcessedMessage
- scrollSeekPlaceHolder
- scrollToLatestMessageOnFocus
- shouldGroupByUser
- separateGiphyPreview
- showUnreadNotificationAlways
- stickToBottomScrollBehavior
- reactionDetailsSort
- sortReactions
- threadList