Info:
This is release candidate documentation for Stream Chat React Native SDK v10. For the latest stable version, see the latest version (v9).

Message composer UI

The message composer UI is where a user writes, edits and sends a message. The message being written lives in a MessageComposer object from stream-chat: its text, attachments, quoted message and poll. The UI renders that object and calls it when the user acts. This page covers which composer the UI drives, the parts of the UI, the attachment picker and voice recording, and what each part reads. The props are in the MessageComposer reference, and the object is on Message composer.

Which composer it drives

<MessageComposer /> and every part inside it work on the composer that useMessageComposer() returns, the first of:

  1. the composer of the message being edited, while the user edits one;
  2. the thread's composer, thread.messageComposer, inside a Thread;
  3. the channel's composer, channel.messageComposer.

Channel and Thread provide it, so MessageComposer works only inside one of them.

Editing uses a composer of its own. When the user picks "Edit" on a message, the SDK loads the message into a composer for that message, and the composer UI switches to it. What the user was writing stays in the channel's composer. The edited-message preview shows above the input, and sending saves the edit. Sending, or closing the preview, switches back to the channel's or thread's composer. Editing a message that bounced sends it again as a new message.

Sending calls MessageComposer.send(), or MessageComposer.update() for an edit. When the user can't send links in the channel and the text contains one, an alert says so and nothing is sent. When the composer has nothing to send, the text stays in the input.

The composer's content stays in the composer object when the screen unmounts, so the channel list row shows it as a draft until it's sent or cleared. MessageComposer doesn't save drafts to the server; with drafts turned on, call MessageComposer.createDraft() when the user leaves the screen. With drafts on, MessageComposer inside a Thread loads the thread's saved draft when it mounts. Drafts covers them.

Layout

MessageComposer renders a row, from start to end:

  1. The leading view, MessageComposerLeadingView: the attach button, which opens the attachment picker. It's hidden while a command is active, when the user lacks the upload-file capability, and when no picker is available.
  2. The input box, which holds:
    • the header view, MessageInputHeaderView, with the previews;
    • the input view, InputView: the command chip, the text input, and in a thread the also-send-to-channel checkbox;
    • the trailing view, MessageInputTrailingView, with the send button or its replacement.
  3. The trailing view after the box, MessageComposerTrailingView, which renders nothing by default.

By default the composer sits below the message list, with a border on top. With messageInputFloating on Channel or Thread, it floats over the list instead, and the list leaves room for it.

Previews

The header view shows what the message will carry. Each preview renders only when the composer has something for it:

Preview Shows Override key
Edited message The message being edited, with a close button to cancel. Takes the quoted message's place. Reply
Quoted message The message being replied to, with a close button to remove it Reply
Attachments Images, videos, files and recordings, with their upload progress and a remove button AttachmentUploadPreviewList
Link previews A card for each link in the text None

Reply is the same component that shows a quoted message inside a message bubble. It gets mode="edit" or mode="reply" in the composer.

AttachmentUploadPreviewList renders one preview per attachment through ImageAttachmentUploadPreview, VideoAttachmentUploadPreview, FileAttachmentUploadPreview and AudioAttachmentUploadPreview, and their upload states through ImageUploadInProgressIndicator, ImageUploadRetryIndicator, ImageUploadNotSupportedIndicator and the File... versions of the same three. Each of these is an override key.

The input

The text input reads and writes textComposer: the text, the cursor position, the mentioned users and the active command. additionalTextInputProps on Channel or Thread passes props to the TextInput, and setInputRef gives you its ref.

Typing a trigger opens the suggestion list, AutoCompleteSuggestionList, above the composer:

  • @ suggests people to mention, and, where the channel allows them, @channel, @here, roles and user groups. Each row is a MentionSuggestionItem.
  • / suggests the channel's commands.
  • : suggests emoji, once you add the emoji middleware, createTextComposerEmojiMiddleware, to the text composer. See Emoji.

The suggestions come from the text composer's middleware; Suggestions explains them. The list renders inside the message list, so a screen without a MessageList or MessageFlashList shows none.

Picking a command, such as /giphy, shows it as a chip before the input with a close button that clears it. The attach button hides while a command is active.

The send button and its replacements

The trailing view shows one control, the first of these that applies:

  1. The stop button, StopMessageStreamingButton, while an AI is thinking about or generating a reply in the channel. It calls Channel.stopAIResponse(). See SDK integration.
  2. A checkmark button, while the user edits a message or has a command active. It saves the edit or runs the command, and it's disabled while the composer is empty.
  3. The cooldown timer, CooldownTimer, while slow mode stops the user from sending. It shows the seconds left. See Slow mode.
  4. The record button, StartAudioRecordingButton, when voice recording is on and the composer has no text and no attachments. See Voice recording.
  5. The send button, SendButton, otherwise. It's disabled until the composer has something to send.

In threads

In a thread, a checkbox under the input, "Also send to channel", also posts the reply to the channel. It toggles showReplyInChannel in the composer's state. allowThreadMessagesInChannel={false} on Thread hides it.

When the user can't send

When the user lacks the send-message capability in the channel, for example in a frozen channel, MessageComposer renders SendMessageDisallowedIndicator instead, "You can't send messages in this channel". It waits until the channel's capabilities are known, so it doesn't flash while the channel loads, and it keeps the composer while the user edits a message.

The attachment picker

The attachment picker is a bottom sheet that Channel and Thread render under the composer. The attach button opens it and closes the keyboard. Opening the keyboard closes it, and so does the Android back button.

At its top, a selection bar, AttachmentPickerSelectionBar, has a button for each source:

Button Shows when Opens
Photos hasImagePicker, true when an image picker or media library is installed A grid of the device's photos and videos
Camera hasCameraPicker, true when an image picker is installed The camera
Files hasFilePicker, true when a document picker is installed The system file picker
Poll Polls are on for the channel, the user can send polls, and it isn't a thread The poll creation dialog
Commands hasCommands, true when the channel has commands The list of commands

Below the bar, AttachmentPickerContent shows the selected source. Picked photos, videos and files are uploaded into the composer as they're picked, and show in its previews. compressImageQuality sets how much images are compressed before upload.

Without a media library module, disableAttachmentPicker is true by default: the sheet shows the bar only, and each button opens the system picker directly. Three more props change the picker: attachmentPickerBottomSheetHeight sets its height, shouldRenderAttachmentPicker={false} leaves it out, and handleAttachButtonPress replaces what the attach button does. Which native modules provide each source is on Installation, and the picker's props are in the Attachment picker reference.

Voice recording

Voice recording is off by default. Turn it on with audioRecordingEnabled on Channel and Thread; it needs an audio recording module installed. With it on and the composer empty, the record button replaces the send button:

  1. The user holds the record button for at least asyncMessagesMinimumPressDuration (500 ms by default) to start recording. A short tap shows "Hold to record. Release to save." instead. Without the microphone permission, a notification asks the user to allow it in the settings.
  2. While the user holds it, AudioRecorder replaces the input with the recording time and "Slide to Cancel". Sliding left by asyncMessagesSlideToCancelDistance cancels the recording. Sliding up by asyncMessagesLockDistance locks it, so the user can let go.
  3. A locked recording shows AudioRecordingInProgress, with its waveform, and AudioRecordingLockIndicator. Stopping it shows AudioRecordingPreview, where the user can play the recording before sending or deleting it.
  4. Releasing the button, or sending from the recorder, adds the recording to the composer as a voice recording attachment. With audioRecordingSendOnComplete, it sends the message at once.

Voice recordings and audio playback covers the feature, and the Audio recorder reference its parts.

What each part reads

Each part subscribes to the store of the composer piece it shows, and calls that piece when the user acts:

Composer piece Used by
state The quoted and edited message previews, the also-send-to-channel checkbox
textComposer The input, the suggestion list, the command chip, the send button and its replacements
attachmentManager The attachment previews, the attachment picker, voice recording, the send button
linkPreviewsManager The link previews
pollComposer The poll creation dialog

The composer UI has no part for locationComposer or customDataManager. A part of your own reads them the same way as the others.

A part of your own gets the composer with useMessageComposer() and subscribes to a store with useStateStore. This one counts the characters typed, after the input box, in the slot that is empty by default:

src/ComposerWithCount.tsx
import { StyleSheet, Text } from "react-native";
import type { TextComposerState } from "stream-chat";
import {
  MessageComposer,
  useMessageComposer,
  useStateStore,
  WithComponents,
} from "stream-chat-react-native";

const textSelector = ({ text }: TextComposerState) => ({ text });

const CharacterCount = () => {
  const { textComposer } = useMessageComposer();
  const { text } = useStateStore(textComposer.state, textSelector);

  return <Text style={styles.count}>{text.length}</Text>;
};

const overrides = { MessageComposerTrailingView: CharacterCount };

export const ComposerWithCount = () => (
  <WithComponents overrides={overrides}>
    <MessageComposer />
  </WithComponents>
);

const styles = StyleSheet.create({
  count: { alignSelf: "flex-end", padding: 8 },
});

The same part works in a channel, in a thread and while editing, because the hook returns the composer of the place it's rendered in. ComposerWithCount still has to render inside a Channel or Thread.

Replacing parts

Register replacements with WithComponents. Overriding components explains how overrides apply.

Override key Replaces
Input No default. When set, it replaces the whole row of the composer. Gets additionalTextInputProps and getUsers.
MessageComposerLeadingView The area before the input box, with the attach button
AttachButton The attach button
MessageInputHeaderView The previews above the input
Reply The quoted and edited message previews, and quoted messages in message bubbles
AttachmentUploadPreviewList The attachment previews
InputView The command chip, the text input and the also-send-to-channel checkbox
AutoCompleteSuggestionList The suggestion list
AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem The suggestion list's header, and each suggestion
MentionSuggestionItem Each @ suggestion
MessageInputTrailingView The area with the send button and its replacements
SendButton The send button
CooldownTimer The cooldown timer
StopMessageStreamingButton The stop button
StartAudioRecordingButton The record button
AudioRecorder, AudioRecordingInProgress, AudioRecordingLockIndicator, AudioRecordingPreview, AudioRecordingWaveform The parts of voice recording
MessageComposerTrailingView Nothing by default. Rendered after the input box.
SendMessageDisallowedIndicator What renders when the user can't send
AttachmentPickerSelectionBar, AttachmentPickerContent The attachment picker's bar and content
ImageOverlaySelectedComponent The mark on a photo picked in the picker's grid

The command chip and the also-send-to-channel checkbox have no override key of their own: replace InputView to change them.

The Custom composer UI cookbook builds a composer UI of its own.

Used in