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

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, records 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 Channel or a Thread. It reads the channel, the composer and its settings from them, and throws outside both.
  • Configure the composer on Channel and Thread, not on MessageComposer. Props such as audioRecordingEnabled, hasCommands and messageInputFloating reach the composer's parts and the message list through MessageInputContext. Most of the props MessageComposer accepts don't, as the Props tables show.
  • Change what is sent in the state layer: transform the message with composer middleware, or replace the request with a sendMessageRequest handler, as Customizing requests shows.
  • Change the look with WithComponents. Each part of the composer has an override key, listed on Composer UI components.
  • Declare additionalTextInputProps once, at module scope or in useMemo. MessageComposer re-renders when the object changes.

Basic usage

src/ChatScreens.tsx
import type {
  Channel as StreamChannel,
  Thread as StreamThread,
} from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
  Thread,
} from "stream-chat-react-native";

export const ChannelScreen = ({ channel }: { channel: StreamChannel }) => (
  <Channel audioRecordingEnabled channel={channel}>
    <MessageList />
    <MessageComposer />
  </Channel>
);

export const ThreadScreen = ({ thread }: { thread: StreamThread }) => (
  <Thread audioRecordingEnabled thread={thread}>
    <MessageList />
    <MessageComposer />
  </Thread>
);

The first composer writes into channel.messageComposer, the second into thread.messageComposer. Voice recording is turned on with a prop of Channel and of Thread, because neither passes its configuration to the other.

What it renders

MessageComposer renders the composer row: MessageComposerLeadingView with the attach button, then a box with MessageInputHeaderView (the previews), InputView (the command chip, the text input and the also-send-to-channel checkbox) and MessageInputTrailingView (the send button or its replacement), then MessageComposerTrailingView. While the user records a voice message, the recorder parts take the input's place. Each of these is an override key; Composer UI components lists them with their props, and Audio recorder covers the recorder.

It also renders:

  • in place of everything above, SendMessageDisallowedIndicator, once the channel's capabilities are known and the user lacks send-message, unless a message is being edited;
  • the default poll creation dialog, CreatePoll in a PollModal, while showPollCreationDialog is true (see Poll components);
  • the Input override in place of the row, when you register one. It gets additionalTextInputProps and getUsers(), which returns the users of members and watchers without duplicates.

When it mounts inside a Thread, it loads the thread's saved draft into the thread's composer, when drafts are turned on. When it unmounts, it closes the attachment picker. It doesn't save or clear the composer: see Drafts.

With messageInputFloating, the row floats over the message list, with a shadow, instead of sitting below it. The message list reads the same setting from MessageInputContext to leave room for it, so set it on Channel or Thread.

Props

All props are optional. Without a prop, MessageComposer uses the value it reads from its context or computes itself, which the Default column names.

Prop Type Default Description
additionalTextInputProps TextInputProps the Channel or Thread prop Props for the React Native TextInput, also passed to the Input override. They're spread after the SDK's own, so onChangeText, onSelectionChange, value and style replace the SDK's handling and styles. A placeholder replaces "Send a message", a command's placeholder and the slow mode placeholder. numberOfLines sets how many lines the input grows to, 5 by default.
TextInputComponent TextInputOverrideComponent React Native's TextInput The text input component. It gets the same props as the default and must forward its ref.
closeAttachmentPicker () => void from MessageInputContext Called when MessageComposer unmounts.
showPollCreationDialog boolean from MessageInputContext Whether the default poll creation dialog shows.
closePollCreationDialog () => void from MessageInputContext Closes the default poll creation dialog. Passed to CreatePoll and to PollModal's onRequestClose.
createPollOptionGap number none Passed to CreatePoll: the gap between the options of the poll form, 8 when unset. MessageComposer doesn't read the Channel or Thread prop of the same name from MessageInputContext.
sendMessage () => Promise<void> from MessageInputContext Passed to CreatePoll, which calls it after creating the poll. The send button doesn't use this prop: it reads sendMessage from MessageInputContext.
messageInputFloating boolean from MessageInputContext, false Floats the row over the message list. See What it renders.
messageInputHeightStore MessageInputHeightStore from MessageInputContext The store the row writes its height into. The message list reads the context's store, so replacing it here leaves the list without the height.
asyncMessagesLockDistance number from MessageInputContext, 50 Here only the lock indicator's slide animation reads it. The record button's gesture reads the context's value, so set it on Channel or Thread.
asyncMessagesSlideToCancelDistance number from MessageInputContext, 75 Here only the fade of "Slide to Cancel" reads it. The record button's gesture reads the context's value, so set it on Channel or Thread.
inputBoxRef React.RefObject<InputBoxRef | null> from MessageInputContext Read to style the focused input box and to move screen reader focus to the input on mount. The text input fills the context's ref, not one passed here.
channel Channel the channel of Channel or Thread Read only for getUsers() of the Input override, when members or watchers isn't set.
members MembersState['members'] channel.state members The members getUsers() returns, for the Input override.
watchers ChannelWatchState['watchers'] channel.state watchers The watchers getUsers() returns, for the Input override.
isKeyboardVisible boolean useKeyboardVisibility() Whether the keyboard is open. The row's bottom padding and its place over an open attachment picker depend on it. Meant for the SDK's own use.
isRecordingStateIdle boolean the recorder's state Whether no recording is in progress. While it's false, AudioRecorder takes the input's place. Meant for the SDK's own use.
micLocked boolean the recorder's state Whether the recording is locked. Shows AudioRecordingInProgress above the input. Meant for the SDK's own use.
recordingStatus string the recorder's state "idle", "recording" or "stopped". "stopped" shows AudioRecordingPreview above the input. Meant for the SDK's own use.

Props that have no effect

MessageComposer accepts these props, but none of its parts reads them from MessageComposer. The parts read the values from their contexts instead. Set the configuration ones on Channel or Thread.

Prop Type Where the parts read it
audioRecordingEnabled boolean MessageInputContext, set on Channel or Thread
audioRecordingSendOnComplete boolean MessageInputContext, set on Channel or Thread
asyncMessagesMinimumPressDuration number MessageInputContext, set on Channel or Thread
compressImageQuality number MessageInputContext, set on Channel or Thread
openPollCreationDialog ({ sendMessage }: Pick<LocalMessageInputContext, 'sendMessage'>) => void MessageInputContext, set on Channel or Thread
audioRecorderManager AudioRecorderManager MessageInputContext
uploadNewFile (file: File) => Promise<void> MessageInputContext
clearEditingState () => void MessageComposerAPIContext
editing boolean the composer's editedMessage
isOnline boolean useSettledWSConnectionHealth()
t StreamTFunction TranslationContext

A change to audioRecordingEnabled, audioRecordingSendOnComplete, asyncMessagesMinimumPressDuration, openPollCreationDialog, editing, isOnline or t still re-renders MessageComposer.

Examples

Change the text input

This composer capitalizes sentences and shows a placeholder of its own. The props are declared once, at module scope:

src/ChannelScreen.tsx
import type { TextInputProps } from "react-native";
import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
} from "stream-chat-react-native";

const textInputProps: TextInputProps = {
  autoCapitalize: "sentences",
  placeholder: "Write to the team",
};

export const ChannelScreen = ({ channel }: { channel: StreamChannel }) => (
  <Channel channel={channel}>
    <MessageList />
    <MessageComposer additionalTextInputProps={textInputProps} />
  </Channel>
);

The placeholder replaces the command and slow mode placeholders too. To keep them, set additionalTextInputProps without a placeholder. To set the same props for every composer under a Channel or Thread, pass additionalTextInputProps to it instead.

Focus the input

inputBoxRef in MessageInputContext holds the text input. A part rendered inside the composer, or anywhere under the Channel or Thread, focuses it:

src/FocusComposerButton.tsx
import { Pressable, Text } from "react-native";
import { useMessageInputContext } from "stream-chat-react-native";

export const FocusComposerButton = () => {
  const { inputBoxRef } = useMessageInputContext();

  return (
    <Pressable onPress={() => inputBoxRef.current?.focus()}>
      <Text>Reply</Text>
    </Pressable>
  );
};

Hooks

Composer hooks lists the rest.