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

Composer hooks

These hooks read the message composer and its parts: attachments, polls, commands and uploads. Use them to build composer UI on the same MessageComposer the default components write into.

The composer is explained on Message composer, and its API on MessageComposer API. How the default composer UI is built is on Message composer UI, and how to read state in components on Reading state in components.

Each entry gives the signature, then:

  • Returns: what the hook gives back.
  • Reads: the store or the context it reads.
  • Re-renders: what makes the component render again.
  • Needs: where the component must sit, and what the hook does outside that place.

Most hooks here read the composer from useMessageComposerController(), so they need what it needs: Chat, and a Channel or a Thread.

useMessageComposerController

Returns the composer the component writes into.

useMessageComposerController(): MessageComposer
  • Returns, the first that applies:
    1. the composer a MessageComposerControllerProvider above supplies, such as one editing a message inline;
    2. inside a Thread, thread.messageComposer;
    3. channel.messageComposer.
  • Reads: the composer, thread and channel contexts. It keeps the composer's event subscriptions running while the component is mounted. It subscribes to no store.
  • Re-renders: when the supplied composer, the thread or the channel changes. Not when the composer's state changes: read its stores with useStateStore or the hooks below.
  • Needs: Chat, and a Channel or a Thread. Throws outside them, even below a MessageComposerControllerProvider.

Use it rather than channel.messageComposer: inside a thread it returns the thread's composer.

useMessageComposerControllerContext

Returns the composer a MessageComposerControllerProvider supplies.

useMessageComposerControllerContext(): MessageComposer | undefined
  • Returns: the supplied composer, or undefined when no provider supplies one.
  • Reads: the context of MessageComposerControllerProvider. No store.
  • Re-renders: when the provider supplies another composer.
  • Needs: nothing. Never throws.

Use it to tell a supplied composer apart from the thread's or channel's own, for example to show an "Editing" label.

useMessageComposerContext

Returns the settings and bindings of the MessageComposer around the component.

useMessageComposerContext(): MessageComposerContextValue
  • Returns:
    • textareaRef, onPaste and recordingController: the bindings from useMessageComposerBindings.
    • additionalTextareaProps, asyncMessagesMultiSendEnabled, audioRecordingEnabled, emojiSearchIndex, focus, hideSendButton, maxRows, minRows, parent and shouldSubmit: the MessageComposer props of the same names. emojiSearchIndex falls back to the one in WithComponents.
  • Reads: the context MessageComposer provides. It holds UI settings, not the composition: the text, attachments and the rest are in the composer's stores.
  • Re-renders: when MessageComposer provides a new value.
  • Needs: MessageComposer. Throws outside it. useIsWithinMessageComposerContext() returns whether the component is inside one, without throwing.

The context is on MessageComposerContext.

useMessageComposerSubmitFn

Returns the function that submits the composition.

useMessageComposerSubmitFn(): () => Promise<MessageComposerSubmitResult>
  • Returns: a function that calls MessageComposer.update() while a message is being edited, and MessageComposer.send() otherwise. Its promise never rejects: it resolves to 'sent', 'nothing-to-send' or 'failed'.
  • Reads: whether a message is being edited, when it's called. No store.
  • Re-renders: never by itself.
  • Needs: what useMessageComposerController needs.

The default send button and the Enter key submit through it. Use it in your own submit controls so they submit the same way. What MessageComposer.send() and MessageComposer.update() do is on Message composer.

useMessageComposerBindings

Creates the bindings MessageComposer puts in its context.

useMessageComposerBindings(props: MessageComposerProps): UseMessageComposerBindingsParams
  • Returns:
    • textareaRef: a ref for the text input. It focuses the input on mount when focus is set.
    • onPaste: a paste handler. It inserts pasted text, or uploads pasted files.
    • recordingController: the voice recorder, set up from audioRecordingEnabled, audioRecordingConfig and asyncMessagesMultiSendEnabled.
  • Reads: the composer from useMessageComposerController().
  • Re-renders: when the recorder's state changes.
  • Needs: what useMessageComposerController needs.

You rarely need it. MessageComposer calls it, and its children read the result through useMessageComposerContext(). Call it only when you build a replacement for MessageComposer itself.

useAttachmentManagerState

Returns the attachments in the composer and what can still be added.

useAttachmentManagerState(): { attachments: LocalAttachment[]; attachmentsEnabled: boolean; availableUploadSlots: number; blockedUploadsCount: number; customCdn: boolean; failedUploadsCount: number; hasAvailableUploadSlots: boolean; isUploadEnabled: boolean; locationEnabled: boolean; maxNumberOfFilesPerMessage: number; pendingUploadsCount: number; pollsEnabled: boolean; successfulUploadsCount: number; uploadsInProgressCount: number }
  • Returns:
    • attachments: the attachments in the composition.
    • The counts of attachments in each upload state: pendingUploadsCount, uploadsInProgressCount, successfulUploadsCount, failedUploadsCount, and blockedUploadsCount for files the server's upload settings refused.
    • availableUploadSlots: how many more files fit under maxNumberOfFilesPerMessage. hasAvailableUploadSlots says whether any do.
    • isUploadEnabled: whether the composer accepts a file now. It needs uploads turned on, a free slot, and the upload-file capability unless files go to your own CDN.
    • attachmentsEnabled, customCdn, locationEnabled and pollsEnabled: the composer settings in effect.
  • Reads: attachmentManager.state, field attachments, and messageComposer.configState. The counts and flags are worked out from those on each render.
  • Re-renders: when the attachments change, and when one of those settings changes.
  • Needs: what useMessageComposerController needs.

Uploads and their limits are explained on Attachments and uploads.

useAttachmentsForPreview

Returns what the composer shows above the text input: attachments, a poll and a location.

useAttachmentsForPreview(): { attachments: LocalAttachment[]; location: StaticLocationPreview | LiveLocationPreview | null; poll: PollComposerState["data"] }
  • Returns:
    • attachments: the attachments in the composition.
    • poll: the poll being composed, from the poll composer's form.
    • location: the location to share, or null.
  • Reads: attachmentManager.state (attachments), pollComposer.state (data) and locationComposer.state (location).
  • Re-renders: when one of the three changes.
  • Needs: what useMessageComposerController needs.

The Attachment previews recipe builds a custom preview list.

useCanCreatePoll

Says whether the poll being composed can be created.

useCanCreatePoll(): boolean
  • Returns: true when the poll form is valid: it has a name, at least one option with text, a valid maximum number of votes, and no field errors.
  • Reads: pollComposer.state, through pollComposer.canCreatePoll.
  • Re-renders: when the answer changes. It checks again on every change to the poll form.
  • Needs: what useMessageComposerController needs.

Use it to enable the create button of a custom poll form. Polls are on Polls.

useMessageComposerHasSendableData

Says whether the composition can be sent.

useMessageComposerHasSendableData(): boolean
  • Returns: messageComposer.hasSendableData. It's true when the composition has text, a sendable attachment, a created poll or a location, and the command in it, if any, is ready to send.
    • By default, an attachment counts once its upload finishes, and text and attachments can't be sent while an upload is in progress.
    • When sending with pending uploads is on, attachments still uploading, and failed ones, count too. Blocked ones never do.
  • Reads: messageComposer.editingAuditState, which changes on every change to the composition.
  • Re-renders: on every change to the composition.
  • Needs: what useMessageComposerController needs.

The default send button is disabled while this is false. Sending with pending uploads explains how pending uploads count.

useMessageContentIsEmpty

Says whether the composition has no content.

useMessageContentIsEmpty(): boolean
  • Returns: messageComposer.contentIsEmpty: true when there's no text, no attachment, no created poll and no location. A quoted message doesn't count as content.
  • Reads: messageComposer.editingAuditState.
  • Re-renders: on every change to the composition.
  • Needs: what useMessageComposerController needs.

Unlike useMessageComposerHasSendableData, it doesn't look at uploads or commands. An attachment still uploading makes the content non-empty, although it can't be sent yet.

useMessageComposerCommands

Returns the slash commands available in the composer, and whether each can be used now.

useMessageComposerCommands(): MessageComposerCommand[]
  • Returns: one { command, enabled } for each command the channel offers. command has the command's name, description and args. enabled is false for every command while a message is being edited, and for moderation commands while a message is quoted.
  • Reads: the composer channel's configState, field availableCommands, and messageComposer.state, fields editedMessage and quotedMessage.
  • Re-renders: when the available commands change, and when an edit or a quote starts or ends.
  • Needs: what useMessageComposerController needs.
import { useMessageComposerCommands } from "stream-chat-react";

export const CommandHints = () => {
  const commands = useMessageComposerCommands();

  return (
    <ul>
      {commands.map(({ command, enabled }) => (
        <li aria-disabled={!enabled} key={command.name}>
          /{command.name} {command.args}: {command.description}
        </li>
      ))}
    </ul>
  );
};

The server decides which commands a channel has. Commands are explained on Suggestions and Commands and Giphy.

useAttachmentUploadState

Returns whether an attachment of a message is uploading, and how far along it is.

useAttachmentUploadState(attachment?: Attachment | LocalAttachment | SharedLocationResponse): AttachmentUploadState
  • Returns: { isUploading, progress, uploadConfirmationPending }.
    • isUploading: true while a request for the attachment is in flight.
    • progress: 0 to 100 while uploading, when the size is known. undefined otherwise.
    • uploadConfirmationPending: true once every byte is sent but the server hasn't answered yet. Show an indeterminate indicator then: the bar would sit at 100% with nothing confirmed.
  • Reads: client.uploadManager.state, field uploads: the record of the attachment's request, which exists only while the request runs.
  • Re-renders: when the request's progress or confirmation state changes.
  • Needs: Chat. Throws outside it.

It's meant for attachments on messages sent before their uploads finished. An attachment from the server never matches a request, so it always reports isUploading: false. See Sending with pending uploads.

useAttachmentsUploadState

The same as useAttachmentUploadState, for several attachments at once.

useAttachmentsUploadState(attachments: (Attachment | LocalAttachment | SharedLocationResponse | undefined)[]): AttachmentUploadState
  • Returns: one combined { isUploading, progress, uploadConfirmationPending }.
    • isUploading is true while any of the attachments uploads.
    • progress is the average, once every upload in flight reports one.
    • uploadConfirmationPending is true when any upload waits for the server.
  • Reads: client.uploadManager.state, field uploads.
  • Re-renders: when one of the requests changes.
  • Needs: Chat. Throws outside it.

Use it for one progress bar over a gallery of images.

Composer stores without a hook

Read these stores with useStateStore and your own selector, using the composer from useMessageComposerController():

To read Store
The quoted message, the message being edited, the poll ID messageComposer.state
The text, mentions, the active command and the suggestions textComposer.state
The poll form and its errors pollComposer.state
The location to share locationComposer.state
Link previews linkPreviewsManager.state
Custom data customDataManager.state
The composer's settings in effect messageComposer.configState

The fields of each store are on MessageComposer API. The cooldown of slow mode is on the channel, not the composer: read the seconds left with useCooldownRemaining, or only whether it's on with useIsCooldownActive, which re-renders less often.

Best practices

  • Read composer state through these hooks or useStateStore. Don't copy it into React state.
  • Get the composer from useMessageComposerController(), not from channel.messageComposer: inside a thread, it returns the thread's composer.
  • Submit through useMessageComposerSubmitFn(), so your controls, the Enter key and the default send button submit the same way.
  • Check whether there's something to send with useMessageComposerHasSendableData(), so every control agrees.
  • Change what the composer does in the state layer: its settings in client.config, and composer middleware. Hooks only read it.

Examples

Composer status

import {
  useAttachmentManagerState,
  useCooldownRemaining,
  useMessageComposerHasSendableData,
} from "stream-chat-react";

export const ComposerStatus = () => {
  const { pendingUploadsCount, uploadsInProgressCount } =
    useAttachmentManagerState();
  const canSend = useMessageComposerHasSendableData();
  const cooldownRemaining = useCooldownRemaining();

  return (
    <div>
      <div>Uploads waiting: {pendingUploadsCount + uploadsInProgressCount}</div>
      <div>Can send: {String(canSend)}</div>
      {cooldownRemaining > 0 && <div>Slow mode: {cooldownRemaining}s</div>}
    </div>
  );
};

Send button

A send button for a custom composer. It submits like the default one, stays disabled while there's nothing to send, and counts down in slow mode:

import {
  useCooldownRemaining,
  useMessageComposerHasSendableData,
  useMessageComposerSubmitFn,
} from "stream-chat-react";

export const CustomSendButton = () => {
  const hasSendableData = useMessageComposerHasSendableData();
  const cooldownRemaining = useCooldownRemaining();
  const submit = useMessageComposerSubmitFn();

  return (
    <button
      disabled={!hasSendableData || cooldownRemaining > 0}
      onClick={() => void submit()}
      type="button"
    >
      {cooldownRemaining > 0 ? `Wait ${cooldownRemaining}s` : "Send"}
    </button>
  );
};

The Custom composer recipe puts controls like these together.