Message UI
The message UI is the component that draws one message. Message renders the MessageUI override with no props, and the UI reads the message and its handlers from MessageContext. The default is MessageUI. A message lists the parts it's made of, and A custom MessageUI explains how a replacement applies.
Best practices
- Build a custom message UI from the exported parts, such as
MessageText,MessageTimestampandAttachment. They read the same context, and most need no props;Attachmenttakes the attachments to render. - Handle the kinds of message your app has. The default covers deleted, blocked, bounced and failed messages, polls and quoted messages. System messages go to the
MessageSystemoverride instead. - Set
VirtualMessageonly whenVirtualizedMessageListneeds a different UI. There it wins overMessageUI. - Declare the
overridesobject once, outside the component, so every message doesn't re-render with each render of the parent.
Basic usage
The default MessageUI also takes any MessageContext value as a prop, and a prop wins over the context. A wrapper registered as the override changes one value for every message. This one shows the sender's avatar on every message, the user's own included:
import { MessageUI, WithComponents } from "stream-chat-react";
const MessageUIWithAllAvatars = () => <MessageUI showAvatar />;
const overrides = { MessageUI: MessageUIWithAllAvatars };
export const App = () => (
<WithComponents overrides={overrides}>
{/* the rest of the app */}
</WithComponents>
);UI customization
A message UI of your own composes the parts it needs. This one keeps the actions, a poll, a quoted message, the attachments, reactions, the replies button, the time and the delivery status:
import {
Attachment,
isMessageDeleted,
MessageActions,
MessageDeletedBubble,
MessageReactions,
MessageRepliesCountButton,
MessageStatus,
MessageText,
MessageTimestamp,
Poll,
QuotedMessage,
useChatContext,
useMessageContext,
} from "stream-chat-react";
export const CustomMessageUI = () => {
const { client } = useChatContext();
const { handleAction, message } = useMessageContext();
const poll = message.poll_id
? client.polls.fromState(message.poll_id)
: undefined;
if (isMessageDeleted(message)) return <MessageDeletedBubble />;
return (
<div className="custom-message">
<MessageActions />
{poll && <Poll poll={poll} />}
{message.quoted_message && <QuotedMessage />}
{message.attachments?.length ? (
<Attachment
actionHandler={handleAction}
attachments={message.attachments}
/>
) : null}
<MessageText />
<MessageReactions />
<MessageRepliesCountButton
reply_count={message.reply_count}
thread_participants={message.thread_participants}
/>
<MessageTimestamp />
<MessageStatus />
</div>
);
};MessageRepliesCountButton renders nothing for a message without replies. When clicked, it calls its onClick prop if you pass one, and otherwise the message's handleOpenThread, which calls Message's openThread prop when set and opens the thread through workspace navigation otherwise. The parts and their props are on Message UI components. A shared location travels in message.shared_location, not in message.attachments; Attachments shows how to pass both.
Override paths
| To change | Set |
|---|---|
| Every message, in both lists and a thread's parent | the MessageUI key |
Every message in VirtualizedMessageList only |
the VirtualMessage key |
One part of the default MessageUI |
that part's key, from the table in A message |
| A deleted message, replacing the whole UI | the MessageDeleted key, which receives { message } and has no default |
| A system message | the MessageSystem key |
| The parent message above a thread's replies | the ThreadHead key, typed with the Message props and given only message |
| The items of the actions menu | the messageActionSet prop of MessageActions, see Actions |
Set the keys with WithComponents.
Props
The SDK renders the message UI with no props. The default MessageUI accepts Partial<MessageContextValue>, exported as MessageUIComponentProps, and merges it over the context. These are the values it reads:
| Prop | Type | Default | Description |
|---|---|---|---|
endOfGroup |
boolean |
With groupedByUser, marks the last message of a group, the only one that shows the time and status. |
|
firstOfGroup |
boolean |
Adds the str-chat__virtual-message__wrapper--first class. |
|
groupedByUser |
boolean |
Adds the str-chat__virtual-message__wrapper--group class, and hides the time and status unless endOfGroup. |
|
handleAction |
ActionHandlerReturnType |
from context | Passed to Attachment for the attachments' actions. |
highlighted |
boolean |
from context | Adds the str-chat__message--highlighted class. |
isMessageAIGenerated |
(message: LocalMessage) => boolean |
from context | Renders the text of an AI-generated message with StreamedMessageText, and hides its edited mark. |
isMyMessage |
() => boolean |
from context | Adds the str-chat__message--me or str-chat__message--other class. |
message |
LocalMessage |
from context | The message to draw. |
onUserClick |
ReactEventHandler |
from context | Attached to the avatar. |
onUserHover |
ReactEventHandler |
from context | Attached to the avatar. |
renderText |
RenderTextFunction |
from context | Passed to MessageText or StreamedMessageText. |
showAvatar |
boolean | 'incoming' | 'outgoing' |
'incoming' |
Which messages get the avatar column: all (true), none (false), other users' or the user's own. |
The default MessageUI re-renders when the message, its read and delivery state, its highlight or its grouping changes. Give the other props values that don't change, such as a renderText declared outside the component.
Hooks
useMessageContext()reads the message and its handlers.useComponentContext()reads the override keys, for a part that renders another part.