MessageComposer
MessageComposer renders the message input of a channel or a thread. The message being written lives in a MessageComposer object from stream-chat, such as channel.messageComposer. The component renders that object and calls it when the user types, attaches or sends. Message composer UI explains the parts it renders and which composer it writes into, and Message composer explains the object.
Best practices
- Render it inside a
Channelor aThread. It writes into that thread's or channel's composer, and throws outside both. - Change what is sent in the state layer, not in the component: transform the message with composer middleware, or replace the request with a
sendMessageRequesthandler, as Customizing requests shows. - Change the look with
WithComponents. Scope the override around oneMessageComposerwhen only that composer should differ. - To tell a thread composer from a channel one in your own parts, read
useMessageComposerController().threadId. It's set only for a thread's composer.
Basic usage
import {
Channel,
ChannelHeader,
MessageComposer,
MessageList,
Thread,
ThreadHeader,
} from "stream-chat-react";
export const ChannelPane = () => (
<Channel channel={channel}>
<ChannelHeader />
<MessageList />
<MessageComposer audioRecordingEnabled />
</Channel>
);
export const ThreadPane = () => (
<Thread thread={thread}>
<ThreadHeader />
<MessageList />
<MessageComposer focus />
</Thread>
);The first composer writes into channel.messageComposer, the second into thread.messageComposer.
UI customization
MessageComposer renders the component registered as MessageComposerUI, and provides MessageComposerContext to it. The default MessageComposerUI is built from parts, each with an override key of its own:
- To replace one part, such as the send button or the attachment selector, register it under its key. Message composer UI lists the keys, and Composer UI components has their props.
- To replace the whole layout, register your own
MessageComposerUI. See Input UI.
When unmounted, MessageComposer saves a draft of the composer if drafts are turned on, and then clears the thread's or channel's composer. See Drafts.
Supplying a composer
By default, MessageComposer writes into the composer of the thread or channel it's rendered in. Wrap it in MessageComposerControllerProvider to have it write into a composer you create instead, for example one that edits a message where it sits. Every MessageComposer and composer hook below the provider uses the supplied composer.
A supplied composer belongs to you:
- When
MessageComposerunmounts, it saves the composer's draft if drafts are on for it, but it never clears the composer. Clear it yourself when the composition ends. - Submitting works as for any composer:
MessageComposer.update()while a message is being edited,MessageComposer.send()otherwise.
Edit a message inline
An edit composer has the message as its composition context. This one shows a MessageComposer for the message while the edit is in progress:
import { useState } from "react";
import type { LocalMessage, MessageComposerState } from "stream-chat";
import { MessageComposer as MessageComposerController } from "stream-chat";
import {
MessageComposer,
MessageComposerControllerProvider,
useChatContext,
useStateStore,
} from "stream-chat-react";
const editingSelector = (state: MessageComposerState) => ({
isEditing: state.editedMessage !== null,
});
export const InlineEditor = ({ message }: { message: LocalMessage }) => {
const { client } = useChatContext();
const [composer] = useState(
() =>
new MessageComposerController({
client,
composition: message,
compositionContext: message,
}),
);
const { isEditing } = useStateStore(composer.state, editingSelector);
if (!isEditing) return null;
return (
<MessageComposerControllerProvider messageComposerController={composer}>
<MessageComposer />
</MessageComposerControllerProvider>
);
};Saving the edit, or cancelling it with the ✕ on the edit preview, empties the composer, and the editor disappears. Editing a message in place shows where to render it and how to open it from the Edit action.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
additionalTextareaProps |
Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "defaultValue" | "style" | "disabled" | "value"> |
none | Props for the <textarea> that TextareaComposer renders. A placeholder here replaces "Send a message". The input sets its own className, onBlur, onChange, onKeyDown, onPaste, onScroll and onSelect, so those are ignored here. |
asyncMessagesMultiSendEnabled |
boolean |
false |
Keeps a finished voice recording in the composer, to send with the rest of the message. Without it, the recording is sent as soon as the user finishes it. |
audioRecordingConfig |
CustomAudioRecordingConfig |
none | Settings for the voice recorder. See Audio Recorder. |
audioRecordingEnabled |
boolean |
false |
Shows the record button while the composer is empty, nothing is being edited, no command is active and the browser can record. |
emojiSearchIndex |
EmojiSearchIndex |
the override | Put into MessageComposerContext, falling back to the emojiSearchIndex key of WithComponents. No built-in component reads it. Emoji autocomplete comes from a middleware; see Emoji. |
focus |
boolean |
false |
Focuses the input on mount, and again whenever the attachments or the quoted message change. |
hideSendButton |
boolean |
false |
Removes the control after the input: the send button, the cooldown timer and the record button. While an AI reply is being generated and the stop button isn't turned off, the control still shows. |
maxRows |
number |
10 |
The most rows the input grows to. |
minRows |
number |
none | The fewest rows the input shows. Without it, an empty input is one row high. |
parent |
LocalMessage |
none | Put into MessageComposerContext for your own parts. The SDK doesn't read it: inside a Thread, the composer is the thread's. |
shouldSubmit |
(event: React.KeyboardEvent<HTMLTextAreaElement>) => boolean |
Enter without Shift | Decides which key press submits. The default submits on Enter without Shift, and not while an input method is composing text. |
MessageComposer puts these props into MessageComposerContext, where its parts read them.
Hooks
useMessageComposerController: the composer a component writes into.useMessageComposerContext: the props above and the input's bindings.useMessageComposerSubmitFn: submits the composition the way the send button does.
Composer hooks lists the rest.