MessageList
The MessageList
component renders a scrollable list of messages. 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 MessageList
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.
const { messages } = useChannelStateContext();
The MessageList
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 MessageList
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>
<MessageList />
<MessageInput />
</Channel>
</Chat>
Example 2 - with messages
prop
const customMessages = [
// array of messages
];
<Chat client={client}>
<ChannelList />
<Channel>
<MessageList messages={customMessages} />
<MessageInput />
</Channel>
</Chat>;
Message grouping
The MessageList
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
.
Rendering message text with renderText
function
Default behaviour
The default renderText
function parses a markdown string and outputs a ReactElement
. Under the hood, the output is generated by the ReactMarkdown
component from react-markdown library. The component transforms the markdown to ReactElement
by using remark
parser and remark
and rehype
plugins.
The default remark
plugins used by SDK are:
remark-gfm
- a third party plugin to add GitHub-like markdown support
The default rehype
plugins (both specific to this SDK) are:
- plugin to render user mentions
- plugin to render emojis
Overriding defaults
Custom renderText
function
If you don't want your chat implementation to support markdown syntax by default you can override the default behaviour by creating a custom renderText
function which returns a React node and passing it down to the MessageList
or MessageSimple
component via renderText
property.
For this particular example we'll create a very primitive one which takes the message text passed down to it as a first argument and returns it wrapped in span
element:
const customRenderText = (text) => {
return <span>{text}</span>;
};
const App = () => (
<Chat client={client}>
<Channel>
<Window>
<MessageList renderText={customRenderText} />
</Window>
</Channel>
</Chat>
);
Here's also an example with VirtualizedMessageList
which currently does not accept renderText
directly:
import { MessageSimple } from 'stream-chat-react';
const customRenderText = (text) => {
return <span>{text}</span>;
};
const CustomMessage = (props) => <MessageSimple {...props} renderText={customRenderText} />;
const App = () => (
<Chat client={client}>
<Channel>
<Window>
<VirtualizedMessageList Message={CustomMessage} />
</Window>
</Channel>
</Chat>
);
Custom element rendering
If you feel like the default output is sufficient, but you'd like to adjust how certain ReactMarkdown components look like (like strong
element generated by typing **strong**) you can do so by passing down options to a third argument of the default renderText
function:
Types mention
and emoji
are special case component types generated by our SDK's custom rehype plugins.
import { renderText } from 'stream-chat-react';
const StrongComponent = ({ children }) => <b className='custom-strong-class-name'>{children}</b>;
const MentionComponent = ({ children, node: { mentionedUser } }) => (
<a data-user-id={mentionedUser.id} href={`/user-profile/${mentionedUser.id}`}>
{children}
</a>
);
const App = () => (
<Chat client={client}>
<Channel>
<Window>
<MessageList
renderText={(text, mentionedUsers) =>
renderText(text, mentionedUsers, {
customMarkDownRenderers: { strong: StrongComponent, mention: MentionComponent },
})
}
/>
</Window>
</Channel>
</Chat>
);
Custom remark and rehype plugins
If you would like to extend the array of plugins used to parse the markdown, you can provide your own lists of remark resp. rehype plugins. The logic that determines what plugins are used and in which order can be specified in custom getRehypePlugins
and getRemarkPlugins
functions. These receive the default array of rehype and remark plugins for further customization. Both custom functions ought to be passed to the third renderText()
parameter. An example follows:
It is important to understand what constitutes a rehype or remark plugin. A good start is to learn about the library called react-remark
which is used under the hood in our renderText()
function.
import { renderText, RenderTextPluginConfigurator } from 'stream-chat-react';
import {customRehypePlugin} from './rehypePlugins';
import {customRemarkPlugin} from './remarkPlugins';
const getRehypePlugins: RenderTextPluginConfigurator = (plugins) => {
return [customRehypePlugin, ...plugins];
}
const getRemarkPlugins: RenderTextPluginConfigurator = (plugins) => {
return [customRemarkPlugin, ...plugins];
}
const customRenderText = (text, mentionedUsers) =>
renderText(text, mentionedUsers, {
getRehypePlugins,
getRemarkPlugins
});
const CustomMessageList = () => (
<MessageList renderText={customRenderText}/>
);
It is also possible to define your custom set of allowed tag names for the elements rendered from the parsed markdown. To perform the tree transformations, you will need to use libraries like unist-builder
to build the trees and unist-util-visit
or hast-util-find-and-replace
to traverse the tree:
import { findAndReplace } from 'hast-util-find-and-replace';
import { u } from 'unist-builder';
import { defaultAllowedTagNames, renderText, RenderTextPluginConfigurator } from 'stream-chat-react';
// wraps every letter b in <xxx></xxx> tags
const customTagName = 'xxx';
const replace = (match) => u('element', { tagName: customTagName }, [u('text', match)]);
const customRehypePlugin = () => (tree) => findAndReplace(tree, /b/, replace);
const getRehypePlugins: RenderTextPluginConfigurator = (plugins) => {
return [customRehypePlugin, ...plugins];
}
const customRenderText = (text, mentionedUsers) =>
renderText(text, mentionedUsers, {
allowedTagNames: [...defaultAllowedTagNames, customTagName],
getRehypePlugins,
});
const CustomMessageList = () => (
<MessageList renderText={customRenderText}/>
);
Props
additionalMessageInputProps
Additional props to be passed to the MessageInput
component, available props. It is rendered when editing a message.
Type |
---|
object |
closeReactionSelectorOnClick
If true, picking a reaction from the ReactionSelector
component will close the selector.
Type | Default |
---|---|
boolean | false |
customMessageActions
An object containing custom message actions (key) and function handlers (value). For each custom action a dedicated item (button) in MessageActionsBox
is rendered. The key is used as a display text inside the button. Therefore, it should not be cryptic but rather bear the end user in mind when formulating it.
const customActions = {
'Copy text': (message) => {
navigator.clipboard.writeText(message.text || '');
},
};
<MessageList customMessageActions={customActions} />;
Custom action item "Copy text" in the message actions box:

Type |
---|
object |
disableDateSeparator
If true, disables the injection of date separator UI components in the Channel
MessageList
component.
Type | Default |
---|---|
boolean | false |
disableQuotedMessages
If true, disables the ability for users to quote messages.
Type | Default |
---|---|
boolean | false |
formatDate
Overrides the default date formatting logic, has access to the original date object.
Type |
---|
(date: Date) => string |
getDeleteMessageErrorNotification
Function that returns the notification text to be displayed when the delete message request fails. This function receives the deleted message object as its argument.
Type |
---|
(message: StreamMessage) => string |
getFlagMessageErrorNotification
Function that returns the notification text to be displayed when a flag message request fails. This function receives the flagged message object as its argument.
Type |
---|
(message: StreamMessage) => string |
getFlagMessageSuccessNotification
Function that returns the notification text to be displayed when a flag message request succeeds. This function receives the flagged message object as its argument.
Type |
---|
(message: StreamMessage) => string |
getMuteUserErrorNotification
Function that returns the notification text to be displayed when a mute user request fails. This function receives the muted user object as its argument.
Type |
---|
(user: UserResponse) => string |
getMuteUserSuccessNotification
Function that returns the notification text to be displayed when a mute user request succeeds. This function receives the muted user object as its argument.
Type |
---|
(user: UserResponse) => string |
getPinMessageErrorNotification
Function that returns the notification text to be displayed when a pin message request fails. This function receives the pinned message object as its argument.
Type |
---|
(message: StreamMessage) => string |
groupStyles
Callback function to map each message in the list to a group style ( 'middle' | 'top' | 'bottom' | 'single'
).
Type |
---|
(message: StreamMessage, previousMessage: StreamMessage, nextMessage: StreamMessage, noGroupByUser: boolean) => GroupStyle |
hasMore
Whether the list has more items to load.
Type | Default |
---|---|
boolean | ChannelStateContextValue['hasMore'] |
headerPosition
Position to render the HeaderComponent
in the list.
Type |
---|
number |
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 |
internalInfiniteScrollProps
Additional props for the underlying InfiniteScroll component.
Type |
---|
object |
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'] |
Message
Custom UI component to display an individual message.
Type | Default |
---|---|
component | MessageSimple |
messageActions
Array of allowed message actions (ex: 'edit', 'delete', 'reply'). To disable all actions, provide an empty array.
Type | Default |
---|---|
array | ['edit', 'delete', 'flag', 'mute', 'pin', 'quote', 'react', 'reply'] |
messageLimit
The limit to use when paginating new messages (the page size).
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'] |
noGroupByUser
If true, turns off message UI grouping by user.
Type | Default |
---|---|
boolean | false |
onlySenderCanEdit
If true, only the sender of the message has editing privileges. If false
also channel capability update-any-message
has to be enabled in order a user can edit other users' messages.
Type | Default |
---|---|
boolean | false |
onMentionsClick
Custom action handler function to run on click on a @mention in a message.
Type | Default |
---|---|
function | ChannelActionContextValue['onMentionsClick'] |
onMentionsHover
Custom action handler function to run on hover over a @mention in a message.
Type | Default |
---|---|
function | ChannelActionContextValue['onMentionsHover'] |
onUserClick
Custom action handler function to run on click of user avatar.
Type |
---|
(event: React.BaseSyntheticEvent, user: User) => void |
onUserHover
Custom action handler function to run on hover of user avatar.
Type |
---|
(event: React.BaseSyntheticEvent, user: User) => void |
openThread
Custom action handler to open a Thread
component.
Type | Default |
---|---|
function | ChannelActionContextValue['openThread'] |
pinPermissions
The user roles allowed to pin messages in various channel types (deprecated in favor of channelCapabilities
).
Type | Default |
---|---|
object | defaultPinPermissions |
renderText
Custom function to render message text content.
Type | Default |
---|---|
function | renderText |
retrySendMessage
Custom action handler to retry sending a message after a failed request.
Type | Default |
---|---|
function | ChannelActionContextValue['retrySendMessage'] |
returnAllReadData
If true, readBy
data supplied to the Message
components will include all user read states per sent message. By default,
only readBy
data for a user's most recently sent message is returned.
Type | Default |
---|---|
boolean | false |
scrolledUpThreshold
The pixel threshold to determine whether the user is scrolled up in the list. When scrolled up in the active
channel, the MessageNotification
component displays when new messages arrive.
Type | Default |
---|---|
number | 200 |
threadList
If true, indicates that the current MessageList
component is part of a Thread
.
Type | Default |
---|---|
boolean | false |
unsafeHTML
If true, renders HTML instead of markdown. Posting HTML is only supported server-side.
Type | Default |
---|---|
boolean | false |