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 an edit composer 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". className is added to the textarea's classes. onPaste runs before the composer's paste handling; see TextareaComposer props. <TextareaComposer/> sets its own onBlur, onChange, onCompositionStart, onCompositionEnd, onKeyDown, onScroll, onSelect and disabled, and its aria-activedescendant, aria-autocomplete, aria-controls and aria-label, 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. A new object with the same values keeps the recorder; Encoding recordings as MP3 says when it's replaced. |
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. |
focus |
boolean |
false |
Focuses the input on mount, and again whenever the attachments or the quoted message change. |
generateRecordingTitle |
(mimeType: string) => string |
audio_recording_<ISO date>.<extension> |
Names the file of a voice recording from the type of the final file: the encoder's output, such as audio/wav, or Safari's recording as is. See Recording file names. |
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. |
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. audioRecordingConfig and generateRecordingTitle are the exception: they set up the voice recorder, and the context doesn't carry 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.