# Composer hooks

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/), and its API on [MessageComposer API](https://getstream.io/chat/docs/sdk/react/v15/reference/state/messagecomposer-api/). How the default composer UI is built is on [Message composer UI](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-composer-ui/), and how to read state in components on [Reading state in components](https://getstream.io/chat/docs/sdk/react/v15/ui/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.

```ts
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.

```ts
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.

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message-composer/messagecomposercontext/).

## useMessageComposerSubmitFn

Returns the function that submits the composition.

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/#editing).

## useMessageComposerBindings

Creates the bindings `MessageComposer` puts in its context.

```ts
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.

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/#attachments-and-uploads).

## useAttachmentsForPreview

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

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/recipes/composer/attachment-previews/) recipe builds a custom preview list.

## useCanCreatePoll

Says whether the poll being composed can be created.

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/polls/).

## useMessageComposerHasSendableData

Says whether the composition can be sent.

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/sending-with-pending-uploads/) explains how pending uploads count.

## useMessageContentIsEmpty

Says whether the composition has no content.

```ts
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.

```ts
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.

```tsx
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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/suggestions/#commands) and [Commands and Giphy](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/commands-and-giphy/).

## useAttachmentUploadState

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

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/sending-with-pending-uploads/).

## useAttachmentsUploadState

The same as `useAttachmentUploadState`, for several attachments at once.

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/reference/state/messagecomposer-api/). The cooldown of slow mode is on the channel, not the composer: read the seconds left with [`useCooldownRemaining`](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/channel-and-thread-hooks/#usecooldownremaining), or only whether it's on with [`useIsCooldownActive`](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/channel-and-thread-hooks/#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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/composer-middleware/). Hooks only read it.

## Examples

### Composer status

```tsx
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:

```tsx
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](https://getstream.io/chat/docs/sdk/react/v15/recipes/composer/custom-composer/) recipe puts controls like these together.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/composer-hooks/](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/composer-hooks/).