Quoted replies
What users get
A quoted reply answers a specific message without leaving the conversation: the reply carries a copy of the message it answers. It works in channels and threads alike.
- Quoting. Quote Reply in a message's menu shows the message above the composer's input and moves the focus to the input. The ✕ on the preview removes the quote.
- The sent reply. The quoted message appears at the top of the reply's bubble, with its author, its text or a preview of its attachments.
- Jumping. Clicking the quoted message jumps to the original in the list and highlights it, loading older messages when it isn't loaded.
- Staying current. If the quoted message is edited while the user is writing, the preview updates. If it's deleted, the quote is removed.
How it works across the layers
The user wants to answer Ben's question from earlier in "Design":
- They pick Quote Reply on Ben's message. The action calls
MessageComposer.setQuotedMessage(message)on the composer they type in: the channel's, or the thread's inside a thread. - The composer keeps the message in its state as
quotedMessage.QuotedMessagePreviewreads it and shows it above the input. - The user sends their reply. The composer adds
quoted_message_idto the request, and a copy of the quoted message to the local message, so the reply shows the quote while it's sending. - In the message list,
QuotedMessagerendersmessage.quoted_message. Clicking it callsMessagePaginator.jumpToMessage()on the list's message paginator.
While a message is quoted, commands of the moderation set are disabled in the composer. A draft keeps its quote, and restoring the draft brings it back. Removing the quote is MessageComposer.setQuotedMessage(null).
The composer's state and its parts are on Message composer, and the composer's previews on Message composer UI. How the list jumps to a message is on Jumping and highlighting.
Turning it on or off
Quoted replies are always available. Two things decide who can use them:
- The
quote-messagecapability. Without it, the message menu leaves out Quote Reply. The server derives it from the user's role and the channel type's settings, which includequotes. - The
disableQuotedMessagesprop ofMessageList, which removes Quote Reply from that list.VirtualizedMessageListdoesn't have it; remove the action from the action set there instead, as described on Actions.
Customizing
Quoting from your own control
Any component inside a Channel or Thread can quote a message through the composer:
import type { LocalMessage } from "stream-chat";
import { useMessageComposerController } from "stream-chat-react";
export const QuoteButton = ({ message }: { message: LocalMessage }) => {
const composer = useMessageComposerController();
return (
<button onClick={() => composer.setQuotedMessage(message)} type="button">
Quote
</button>
);
};To put it in the message menu, add it to the message action set; see Actions.
Replacing the preview in the composer
QuotedMessagePreview is an override key. A replacement reads quotedMessage from the composer's state:
import type { MessageComposerState } from "stream-chat";
import {
useMessageComposerController,
useStateStore,
WithComponents,
} from "stream-chat-react";
const quoteSelector = (state: MessageComposerState) => ({
quotedMessage: state.quotedMessage,
});
const CompactQuotePreview = () => {
const composer = useMessageComposerController();
const { quotedMessage } = useStateStore(composer.state, quoteSelector);
if (!quotedMessage) return null;
return (
<div className="compact-quote-preview">
Replying to {quotedMessage.user?.name ?? "a message"}
<button onClick={() => composer.setQuotedMessage(null)} type="button">
Cancel
</button>
</div>
);
};
export const App = () => (
<WithComponents overrides={{ QuotedMessagePreview: CompactQuotePreview }}>
{/* the rest of the app */}
</WithComponents>
);While the user edits a message, the composer shows EditedMessagePreview in its place.
Replacing the quote in a message
QuotedMessage renders the quote inside a sent message. A replacement reads the message from useMessageContext():
import {
useMessageContext,
useMessagePaginator,
WithComponents,
} from "stream-chat-react";
const QuoteLine = () => {
const { message } = useMessageContext();
// Inside a thread, the original is in the thread's replies, which this list holds.
const messagePaginator = useMessagePaginator();
const quoted = message.quoted_message;
if (!quoted) return null;
return (
<button
className="quote-line"
onClick={() => messagePaginator.jumpToMessage(quoted.id)}
type="button"
>
{quoted.user?.name}: {quoted.text}
</button>
);
};
export const App = () => (
<WithComponents overrides={{ QuotedMessage: QuoteLine }}>
{/* the rest of the app */}
</WithComponents>
);The jump itself is explained on Jumping and highlighting.
Reference
- State: Message composer and the MessageComposer API.
- UI: Message composer UI, Message list UI and Overriding components.
- Props: Composer UI components, Message UI components and MessageList.
- Hooks: Composer hooks.