MessageInputContext
MessageInputContext holds the composer settings set on Channel or Thread, and the functions the composer UI calls: send, the pickers, the attachment picker and the voice recorder. Channel and Thread provide it to everything they render, and every part of the composer UI reads it with useMessageInputContext(). It holds UI settings and actions, not the message: the text, the attachments and the rest are in the composer's stores, which Message composer UI maps to the parts that read them.
Best practices
- Read the message being written from the composer, with
useMessageComposer()anduseStateStore, not from this context. - Send with
sendMessage()from this context, so your controls send, edit and check links the way the default send button does. - Set the values on
ChannelandThread. The context is memoized and gets a new value only whenhasCommandschanges or the poll creation dialog opens or closes, so a prop changed after mount, such asaudioRecordingEnabled, doesn't reach it. Remount theChannelorThread, for example with akey, to apply it. - Read the cooldown with
useCooldownRemaining. It isn't in this context. - Render components that read it inside a
Channelor aThread.useMessageInputContext()throws outside both.
Basic usage
This button sits next to the composer and opens the attachment picker on the photo grid:
import { Pressable, Text } from "react-native";
import { useMessageInputContext } from "stream-chat-react-native";
export const AddPhotosButton = () => {
const { hasImagePicker, openAttachmentPicker } = useMessageInputContext();
if (!hasImagePicker) return null;
return (
<Pressable onPress={openAttachmentPicker}>
<Text>Add photos</Text>
</Pressable>
);
};openAttachmentPicker() closes the keyboard, selects the photo grid and opens the sheet. It does nothing when shouldRenderAttachmentPicker is false on Channel or Thread.
Values
Each Channel and Thread creates its own audioRecorderManager, inputBoxRef and messageInputHeightStore, so a channel screen and a thread screen don't share a recorder or an input.
| Value | Type | Description |
|---|---|---|
sendMessage |
() => Promise<void> |
Sends the composition with MessageComposer.send(), or saves the edit with MessageComposer.update() while a message is being edited. Saving the edit of a bounced message sends it again as a new message. It clears the input first, and puts the text back when there was nothing to send. When the text has a link and the user lacks send-links, it shows an alert and sends nothing. |
inputBoxRef |
React.RefObject<InputBoxRef | null> |
The text input. Besides the TextInput methods, such as focus(), it has clearState() and restoreState(text), which set the input's text without changing the composer. |
setInputBoxRef |
Ref<InputBoxRef> | undefined |
The ref callback the text input uses. It fills inputBoxRef and calls setInputRef. |
setInputRef |
(ref: TextInputRef | null) => void |
The Channel or Thread prop. Receives the text input, and null when it unmounts. |
additionalTextInputProps |
TextInputProps |
The Channel or Thread prop. MessageComposer spreads it onto the TextInput. |
messageInputFloating |
boolean |
The Channel or Thread prop, false by default. The composer floats over the message list, and the list leaves room for it. |
messageInputHeightStore |
MessageInputHeightStore |
A store whose store holds the composer's measured height. The message list reads it to leave room for a floating composer. |
openAttachmentPicker |
() => void |
Closes the keyboard, selects the photo grid and opens the attachment picker. Does nothing when shouldRenderAttachmentPicker is false. |
closeAttachmentPicker |
() => void |
Clears the selected source and closes the attachment picker. |
focusInputOnPickerClose |
boolean |
The Channel or Thread prop, true by default. When the user presses the attach button while the picker is open, true focuses the input, which opens the keyboard and closes the picker, and false closes the picker without focusing. |
handleAttachButtonPress |
() => void |
The Channel or Thread prop. Replaces what the attach button does, which is to open or close the picker. |
attachmentPickerBottomSheetHeight |
number |
The Channel or Thread prop: the height of the picker when open. 333 by default, or 72 when disableAttachmentPicker is true. |
attachmentSelectionBarHeight |
number |
The Channel or Thread prop, 72 by default. No component reads it. |
hasImagePicker |
boolean |
The Channel or Thread prop. Whether the picker shows the photos button. By default, whether an image picker or a media library is installed. |
hasCameraPicker |
boolean |
The Channel or Thread prop. Whether the picker shows the camera button. By default, whether an image picker is installed. |
hasFilePicker |
boolean |
The Channel or Thread prop. Whether the picker shows the files button. By default, whether a document picker is installed. |
hasCommands |
boolean |
The Channel or Thread prop. Whether the picker shows the commands button. By default, whether the channel type has commands. |
pickAndUploadImageFromNativePicker |
() => Promise<void> |
Opens the native image picker for as many images as the message has room for, and uploads the picked ones into the composer. Asks the user to open the settings when access was denied. |
takeAndUploadImage |
(mediaType?: MediaTypes) => Promise<{ askToOpenSettings?: boolean; canceled?: boolean } | undefined> |
Opens the camera for a photo, a video or both ("image", "video" or "mixed"), and uploads the result into the composer. Resolves to the camera's result, with askToOpenSettings when access was denied and cancelled when the user closed the camera (the type spells it canceled), or to undefined when the message has no room for another file. |
pickFile |
() => Promise<void> |
Opens the system file picker and uploads the picked files into the composer. Does nothing without a document picker. |
uploadNewFile |
(file: File) => Promise<void> |
Uploads a file into the composer through its attachment manager. It sets the file's type from its name when missing, and compresses images with compressImageQuality first. A file the upload rules block is removed from the composer. |
compressImageQuality |
number |
The Channel or Thread prop, from 0 to 1. Without it, picked images upload as they are, and photos the camera takes use the camera handler's own quality. |
audioRecordingEnabled |
boolean |
The Channel or Thread prop, false by default. Whether the record button shows while the composer is empty. |
audioRecordingSendOnComplete |
boolean |
The Channel or Thread prop, false by default. Whether a finished recording is sent at once, instead of waiting in the composer. |
asyncMessagesMinimumPressDuration |
number |
The Channel or Thread prop, 500 by default: how long, in milliseconds, the user holds the record button to start recording. |
asyncMessagesSlideToCancelDistance |
number |
The Channel or Thread prop, 75 by default: how far, in points, the user slides to the left to cancel a recording. Releasing past half of it cancels. |
asyncMessagesLockDistance |
number |
The Channel or Thread prop, 50 by default: how far, in points, the user slides up to lock a recording. Releasing past half of it locks. |
audioRecorderManager |
AudioRecorderManager |
The recorder. Its state store holds status ("idle", "recording" or "stopped"), duration in milliseconds, waveformData, micLocked, isStarting and the recording. |
startVoiceRecording |
() => Promise<boolean | undefined> |
Starts recording. Resolves to whether the microphone permission was granted. |
stopVoiceRecording |
() => Promise<void> |
Stops recording and keeps the recording, for the preview. |
deleteVoiceRecording |
() => Promise<void> |
Stops recording and throws the recording away. |
uploadVoiceRecording |
(sendOnComplete: boolean) => Promise<void> |
Stops recording and turns the recording into a voiceRecording attachment. With true, it adds the attachment and sends the message at once; with false, it uploads the attachment into the composer. |
openPollCreationDialog |
({ sendMessage }: Pick<LocalMessageInputContext, 'sendMessage'>) => void |
Called by the poll button of the picker. It calls the Channel or Thread prop of the same name with { sendMessage } when you set one, and opens the default poll creation dialog otherwise. |
showPollCreationDialog |
boolean |
Whether the default poll creation dialog is open. |
closePollCreationDialog |
() => void |
Closes the default poll creation dialog. |
createPollOptionGap |
number |
The Channel or Thread prop. The default MessageComposer doesn't pass it to the poll form, which uses 8 unless MessageComposer gets a createPollOptionGap prop of its own. |
MessageInputContextValue is the type of the value, and MessageInputContext itself is exported, so useContext(MessageInputContext) returns the same as useMessageInputContext() without the check for a provider. InputBoxRef, TextInputRef, File and LocalMessageInputContext are exported types.
MessageComposerAPIContext
Channel and Thread also provide MessageComposerAPIContext, with the functions that start and end an edit and set the quoted message. useMessageComposerAPIContext() reads it, and throws outside both.
| Value | Type | Description |
|---|---|---|
setEditingState |
(message?: LocalMessage) => void |
Starts editing message: it takes the message's composer from client.messageComposerCache, or creates one, and loads the message into it. useMessageComposer() returns that composer until the edit ends. Without a message, it ends the edit. |
clearEditingState |
() => void |
Ends the edit without saving it. |
setQuotedMessage |
MessageComposer['setQuotedMessage'] |
Sets the quoted message of the composer useMessageComposer() returns, or clears it with null. |
An edit also ends when the edit composer's editedMessage is cleared, which saving the edit does. Sending, editing and deleting shows the edit flow.
Examples
Insert text and focus the input
A part that writes into the composer gets the composer from useMessageComposer() and the input from inputBoxRef. This leading view keeps the attach button and adds a greeting button:
import { Pressable, Text, View } from "react-native";
import {
MessageComposerLeadingView,
useMessageComposer,
useMessageInputContext,
} from "stream-chat-react-native";
export const LeadingViewWithGreeting = () => {
const { inputBoxRef } = useMessageInputContext();
const { textComposer } = useMessageComposer();
const greet = () => {
textComposer.insertText({ text: "Hi there! " });
inputBoxRef.current?.focus();
};
return (
<View style={{ alignItems: "center", flexDirection: "row" }}>
<MessageComposerLeadingView />
<Pressable onPress={greet}>
<Text>Greet</Text>
</Pressable>
</View>
);
};Register it as MessageComposerLeadingView with WithComponents. It renders the default view, so the attach button stays.
Send from your own button
sendMessage() sends what the composer holds, or saves the edit. Pair it with useMessageComposerHasSendableData(), which the default send button uses to enable itself:
import { Pressable, Text } from "react-native";
import {
useMessageComposerHasSendableData,
useMessageInputContext,
} from "stream-chat-react-native";
export const TextSendButton = () => {
const { sendMessage } = useMessageInputContext();
const hasSendableData = useMessageComposerHasSendableData();
return (
<Pressable disabled={!hasSendableData} onPress={sendMessage}>
<Text>Send</Text>
</Pressable>
);
};Register it as SendButton. The default trailing view still shows the record button, the cooldown timer and the edit button in their cases, and your button otherwise.
Hooks
useMessageInputContextreturns this context.useMessageComposerAPIContextreturns the edit and quote functions.useMessageComposerreturns the composer itself.useAudioRecordercreates the recorder functions.ChannelandThreadcall it already.