# Message composer

A message composer holds the message a user is writing: its text, attachments, poll, location and quoted message. It knows whether there is anything to send, and it turns what it holds into a message when the user sends. Every channel and every thread has one.

>
> **Info:** **Example**
>
> The user types "Hello @Bob" in a channel and adds a photo. The channel's composer keeps the text and the mention in its text composer, and the photo and its upload in its attachment manager. When the user presses send:
>
> 1. `channel.messageComposer.send()` runs the composition middleware, which builds the message twice: `localMessage`, the copy the message list shows at once, and `message`, the request the server receives.
> 2. The composer clears itself, before the request goes out, so anything the user types during the round trip is kept.
> 3. The channel's message operations add the message to `channel.messagePaginator` as `sending`, send the request, and replace the message with the server's copy when it answers.
>

```ts
const result = await channel.messageComposer.send();

if (result === "nothing-to-send") {
  // The composer kept its content. A command or upload problem was also published
  // to client.notifications.
}
```

`MessageComposer.send()` and `MessageComposer.update()` never throw. They resolve to one of three results:

- `'sent'`: the request succeeded.
- `'nothing-to-send'`: there was nothing to compose, or a middleware stopped the composition, for example because a command isn't ready or an upload is still running. The composer keeps its content. When a command or a running upload stopped it, the reason has been [published as a notification](https://getstream.io/chat/docs/sdk/react/state/publishing-notifications/).
- `'failed'`: the request failed. The composer was already cleared. The message stays in the channel's or thread's message paginator as `failed`, where the user can retry it, and an `api:message:send:failed` notification is published. For an edit, the notification is `api:message:update:failed`, and the edit is saved by editing again, as [Message operations](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/#message-statuses) explains.

Every method and store of the composer is listed in [MessageComposer API](https://getstream.io/chat/docs/sdk/react/reference/state/messagecomposer-api/). How the message is sent and retried is on [Message operations](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/).

A poll sent on its own doesn't clear the composer: the text, attachments and quoted message next to it stay, ready to send as the next message. [Creating and sending the poll](https://getstream.io/chat/docs/sdk/react/state/writing-messages/polls/#creating-and-sending-the-poll) explains it.

## Composer kinds

| Composer | Where it comes from       | `contextType` | Sends to               |
| -------- | ------------------------- | ------------- | ---------------------- |
| Channel  | `channel.messageComposer` | `'channel'`   | the channel            |
| Thread   | `thread.messageComposer`  | `'thread'`    | the thread, as a reply |

The channel and thread composers are created with their channel or thread and live as long as it does. A thread composer stamps every message with the thread's id as `parent_id`, and the reply lands in `thread.messagePaginator`.

An edit composer, one you build yourself to edit a message in a separate UI, has `contextType` `'message'`. [Editing](#editing) explains it.

Each composer has an `id`, which becomes the id of the message it sends. After a send, the composer takes a new one.

Configuration registered through `client.config` under `messageComposer` applies to every kind; there's no per-channel or per-thread key for it. To apply something to one kind only, check `composer.contextType` in a setup function, as [Composer middleware](https://getstream.io/chat/docs/sdk/react/state/writing-messages/composer-middleware/#adding-middleware) shows.

## Parts

A composer is made of parts. Each part keeps its data in a store of its own, and each has a page of its own, linked below.

| Part                                                                                | Holds                                                                          |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [`textComposer`](https://getstream.io/chat/docs/sdk/react/state/writing-messages/text/)                 | the text, the cursor, mentions, the active command and the open suggestions    |
| [`attachmentManager`](https://getstream.io/chat/docs/sdk/react/state/writing-messages/attachments/)     | attachments and their uploads                                                  |
| [`linkPreviewsManager`](https://getstream.io/chat/docs/sdk/react/state/writing-messages/link-previews/) | previews for the links in the text                                             |
| [`pollComposer`](https://getstream.io/chat/docs/sdk/react/state/writing-messages/polls/)                | the poll being created                                                         |
| [`locationComposer`](https://getstream.io/chat/docs/sdk/react/state/writing-messages/location/)         | a location to share                                                            |
| [`customDataManager`](https://getstream.io/chat/docs/sdk/react/state/writing-messages/custom-data/)     | data of your own                                                               |
| the composer itself                                                                 | the quoted message, the poll id, "also send in channel" and the edited message |

Subscribe to a part's `state` to follow it. Getters such as `textComposer.text` or `composer.quotedMessage` return the value at the moment you read them, and don't tell you when it changes, so they suit event handlers, not rendering. See [Reactive state](https://getstream.io/chat/docs/sdk/react/state/state-and-entities/reactive-state/).

```ts
const { attachmentManager } = channel.messageComposer;

const unsubscribe = attachmentManager.state.subscribeWithSelector(
  ({ attachments }) => ({ attachments }),
  ({ attachments }) => {
    const uploading = attachments.filter(
      (attachment) => attachment.localMetadata.uploadState === "uploading",
    );
    console.log(`${uploading.length} uploads running`);
  },
);
```

## Drafts

A draft is an unsent composition stored on the server. There is one draft per channel and one per thread, so the user finds what they were writing when they come back, on any device. Drafts are off by default, and `messageComposer.drafts.enabled` in `client.config` turns them on, as [Turning it on or off](https://getstream.io/chat/docs/sdk/react/features/messaging/drafts/#turning-it-on-or-off) on Drafts shows.

With drafts on:

| To…                                     | How                                              | What happens                                                                                                                                                                                                                                                             |
| --------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| save the draft                          | `MessageComposer.createDraft()`                  | Creates the draft on the server from the composer's content, or updates it. Nothing is sent when the composer is empty or hasn't changed since the draft was loaded. The UI layer calls it when the composer goes away, for example when the user opens another channel. |
| load the draft                          | a channel query, or `MessageComposer.getDraft()` | A channel query that returns a draft loads it into the channel's composer, and a thread created from server data starts with its draft. `MessageComposer.getDraft()` loads it on demand.                                                                                 |
| delete the draft                        | `MessageComposer.deleteDraft()`                  | Deletes the draft on the server and leaves the composer's content as it is. While the composer's subscriptions run, it's also called when the composer is emptied.                                                                                                       |
| follow draft updates from other devices | `draft.updated` and `draft.deleted` events       | Keep the composer in step with the user's other devices, while the composer's subscriptions run.                                                                                                                                                                         |

Drafts belong to a channel or a thread, not to a message. So while the composer is editing a message, `MessageComposer.createDraft()` and `MessageComposer.deleteDraft()` do nothing: unsaved changes to the edit aren't kept, and the channel's or thread's draft stays as it was. Drafts are also skipped for a channel that doesn't exist on the server yet. See [Drafts](https://getstream.io/chat/docs/sdk/react/features/messaging/drafts/).

## Editing

Editing modifies the composition of an existing message. The message is loaded into a composer, changed there like a new message, and saved on the server with `MessageComposer.update()`.

The message can be loaded into the channel's or thread's composer, or into an edit composer created just for it. The choice decides what happens to text the user hasn't sent yet:

>
> **Info:** **Example**
>
> The user is typing a new message, "See you at", when they notice a typo in an earlier message and choose Edit.
>
> - **In the channel's composer**, the edited message replaces "See you at". After the edit is saved, the composer is empty, and "See you at" is gone.
> - **In an edit composer**, the message opens in a second composer, for example in a dialog. "See you at" stays in the channel's composer, and the user carries on after saving the edit.
>

### Editing in the channel's or thread's composer

Load the message into the composer:

```ts
const composer = channel.messageComposer;

composer.initState({ composition: message });
// composer.editedMessage is now the message; text, attachments and mentions are loaded

await composer.textComposer.handleChange({
  selection: { end: 13, start: 13 },
  text: "Fixed a typo.",
});

const result = await composer.update();
```

| To…                         | Call                        | What happens                                                                                                                                                                                                                                                                                                                                                                                 |
| --------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| save the edit               | `MessageComposer.update()`  | Saves the edit on the server and clears the composer, which ends the edit. The new text shows at once in the paginator that holds the message: `channel.messagePaginator`, or `thread.messagePaginator` for a reply. If the request fails, the message keeps the new text in the paginator, its `status` becomes `'failed'`, and `error` says why. To save the edit, edit the message again. |
| stop editing without saving | `MessageComposer.clear()`   | Empties the composer, which ends the edit.                                                                                                                                                                                                                                                                                                                                                   |
| undo the changes            | `MessageComposer.restore()` | Puts the original message back into the composer, and the edit continues from there.                                                                                                                                                                                                                                                                                                         |

While a message is being edited, commands can't be used and drafts aren't saved.

### Editing in an edit composer

Create a composer for the message:

```ts
import { MessageComposer } from "stream-chat";

const editComposer = new MessageComposer({
  client,
  compositionContext: message,
  composition: message,
});
```

The message must carry its `cid`, which tells the composer which channel it belongs to. When the message is a reply and the client holds its thread (`client.threads.get(message.parent_id)` returns it), the edit goes through that thread, and the thread's paginator shows it. Otherwise it goes through the channel. Saving works the same way, with `MessageComposer.update()`.

The UI layer has to be given the edit composer to show it. [Edit a message inline](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/messagecomposer/#edit-a-message-inline) shows how in React.

## Pitfalls

- **Saving an edit with `MessageComposer.send()`.** `MessageComposer.send()` always creates a message. On a composer holding an edit it reuses the message's id, so the server rejects the request as a duplicate and `MessageComposer.send()` returns `'failed'`, while the message paginator already shows the edited text. Check `composer.editedMessage` and call `MessageComposer.update()`.
- **A server update during an edit.** A `message.updated` or reaction event for the message being edited reloads the composer from the server's copy, and unsaved changes are lost.
- **Upload settings on one composer.** Every upload in a channel goes through the channel composer's upload settings, including uploads from a thread or edit composer. Register `doUploadRequest` and `trackUploadProgress` for all composers through `client.config`, not on a single composer.
- **Reading getters while rendering.** Getters don't notify. Subscribe to the part's `state`.

## Used in

- [Sending, editing and deleting](https://getstream.io/chat/docs/sdk/react/features/messaging/sending-editing-and-deleting/)
- [Drafts](https://getstream.io/chat/docs/sdk/react/features/messaging/drafts/)
- [Quoted replies](https://getstream.io/chat/docs/sdk/react/features/messaging/quoted-replies/)
- [Attachments and uploads](https://getstream.io/chat/docs/sdk/react/features/rich-content/attachments-and-uploads/)
- [Polls](https://getstream.io/chat/docs/sdk/react/features/rich-content/polls/)
- [Location sharing](https://getstream.io/chat/docs/sdk/react/features/rich-content/location-sharing/)
- [Link previews](https://getstream.io/chat/docs/sdk/react/features/rich-content/link-previews/)
- [Message composer UI](https://getstream.io/chat/docs/sdk/react/ui/components/message-composer-ui/), for the components that render a composer

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-composer/](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-composer/).