# Sending, editing and deleting

## What users get

A message appears in the list the moment the user sends it, marked as sending, and settles when the server answers. Users can edit and delete their own messages, and resend one that failed.

- **Sending.** The message shows at once with a clock icon, which goes away when the server confirms it. The user's latest message then shows whether it was sent, delivered and read.
- **Failed messages.** A message whose request failed stays in the list with an error badge. Its menu offers **Resend**.
- **Editing.** **Edit Message** loads the message into the composer, with an "Edit Message" preview above the input and a checkmark in place of the send button. The ✕ on the preview cancels the edit and puts back whatever the user was typing before. An edited message shows "Edited", with the time of the edit in a tooltip.
- **Deleting.** **Delete message** asks for confirmation, then shows "Message deleted" in place of the message.
- **Bounced messages.** A message that moderation bounced shows in an error style. Clicking it opens a prompt to delete it, edit it, or send it anyway.

## How it works across the layers

The work happens in the state layer: the composer builds the message, and the message operations of the channel or thread change the message list and send the request. The components ask for each step and render the result.

### Sending

The user types "Hello" in the channel "Design" and presses Enter:

1. The `MessageComposer` component calls the state layer's `MessageComposer.send()` on the composer it drives: `channel.messageComposer`, or `thread.messageComposer` inside a thread. See [Which composer it drives](https://getstream.io/chat/docs/sdk/react/ui/components/message-composer-ui/#which-composer-it-drives).
2. The composer builds the message from its text, attachments, mentions and quoted message, and clears itself before the request goes out. The user can start typing the next message right away.
3. `channel.messageOperations.send()` adds "Hello" to the message list with status `sending`. It sends the request through the `sendMessageRequest` handler when one is registered, and through the default request otherwise.
4. When the server answers, its copy replaces the local one with status `received`. Every list that shows the message updates.
5. If the request fails, the message stays in the list with status `failed`, and the composer publishes an error notification.

`MessageComposer.send()` never throws. It resolves to `'sent'`, `'nothing-to-send'` or `'failed'`. [Message composer](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-composer/) explains each, and [Message operations](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/) the statuses and the request handlers.

### Resending

**Resend** calls `handleRetry` from the message context, which calls `MessageOperations.retry({ localMessage })` on the channel or the thread. The message goes back to `sending`.

A message that failed with a 403 error can't be resent. How long a failed request is kept for a retry is on [Message statuses](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/#message-statuses).

### Editing

**Edit Message** loads the message into the composer the user types in, the channel's or the thread's, with `MessageComposer.initState({ composition: message })`. The composer now holds the message as `editedMessage`. The focus moves to that composer's input, with the caret after the text. The SDK finds the composer from the menu's place in the page, in the nearest thread or channel. If no input shows there, for example while a voice recording runs, the focus stays where it was.

Pressing the checkmark calls `MessageComposer.update()` instead of `MessageComposer.send()`. `MessageOperations.update()` shows the new text at once and marks the message edited. If the request fails, the edit is kept and the message is marked `failed`. Like `MessageComposer.send()`, `MessageComposer.update()` never throws and resolves to the same results.

The ✕ on `EditedMessagePreview` ends the edit and restores what the composer held before it started. Saving the edit leaves the composer empty. See [Editing](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-composer/#editing) for the composer's side.

### Deleting

**Delete message** opens a confirmation. Confirming calls `handleDelete` from the message context, which calls `MessageOperations.delete({ localMessage })` on the channel or the thread. The message shows as deleted at once. If the server rejects the request, it comes back.

`MessageOperations.delete()` can also hard-delete the message or delete it for the current user only: see [Operations](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/#operations). What each operation does before and after the server answers is on [Optimistic updates](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/#optimistic-updates).

### Bounced messages

When moderation bounces a message, the server returns it with type `error` and the moderation action `bounce`. The message list shows it in an error style, and the message becomes clickable.

Clicking it opens `MessageBounceModal` with `MessageBouncePrompt`, which offers three choices from `useMessageBounceContext()`:

- **Delete** removes the message from the channel's message list, without a request. A bounced reply stays in its thread.
- **Edit Message** loads it into the composer, as the Edit action does.
- **Send Anyway** resends it with `MessageOperations.retry()`.

### What renders each part

| Part                                      | Component                                                                | Reads                                               |
| ----------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------- |
| The input and the send button             | `MessageComposer`                                                        | the channel's, thread's or edit composer            |
| The preview above the input while editing | `EditedMessagePreview`                                                   | `composer.state.editedMessage`                      |
| Sending, sent, delivered and read icons   | `MessageStatus`                                                          | the message's `status` and the read state           |
| "Edited"                                  | `MessageEditedIndicator`                                                 | `message.message_text_updated_at`                   |
| A deleted message                         | `MessageDeletedBubble`, or `MessageDeleted` to replace the whole message | `deleted_at`, `type: 'deleted'` or `deleted_for_me` |
| The bounce prompt                         | `MessageBouncePrompt`                                                    | `useMessageBounceContext()`                         |
| Edit, Delete and Resend                   | `MessageActions`, with the default message action set                    | the user's capabilities and the message state       |

## Turning it on or off

Sending, editing and deleting are always available. The user's capabilities in the channel decide who can use them:

| Action           | Capability                                   |
| ---------------- | -------------------------------------------- |
| Send a message   | `send-message`                               |
| Edit a message   | `update-own-message` or `update-any-message` |
| Delete a message | `delete-own-message` or `delete-any-message` |

The message actions follow these capabilities: **Edit Message** and **Delete message** appear only when the user may use them. A message that is still sending has no actions, a failed message gets at most **Resend**, **Edit Message** and **Delete message**, and a bounced message has its choices in the bounce prompt instead of a menu. The full rules are on [Message actions](https://getstream.io/chat/docs/sdk/react/features/messaging/message-actions/#how-it-works-across-the-layers).

The server checks `send-message`, but the default composer doesn't hide itself when the user lacks it. To show something else instead, check the capability:

```tsx
import {
  MessageComposer,
  useChannel,
  useChannelCapabilities,
} from "stream-chat-react";

export const ComposerIfAllowed = () => {
  const channel = useChannel();
  const capabilities = useChannelCapabilities({ cid: channel.cid });

  if (!capabilities.has("send-message")) {
    return (
      <p className="composer-read-only">You can't post in this channel.</p>
    );
  }

  return <MessageComposer />;
};
```

## Customizing

### Changing the request

Register a request handler in `client.config` to change how a message is sent, edited or deleted. A handler receives the default request, so it can wrap it. This one records every sent message:

```ts
client.config.set({
  channel: {
    requestHandlers: {
      sendMessageRequest: async (params, defaultRequest) => {
        const response = await defaultRequest(params);
        analytics.track("message_sent", { cid: params.localMessage.cid });
        return response;
      },
    },
  },
});
```

The handlers for editing and deleting are `updateMessageRequest` and `deleteMessageRequest`, and `retrySendMessageRequest` handles resends. Threads use their channel's handlers. See [Customizing requests](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/#customizing-requests).

### Changing the actions

Edit, Delete and Resend are items of the message action set. To remove, replace or add actions, see [Actions](https://getstream.io/chat/docs/sdk/react/ui/components/actions/#message-actions) and [Message actions](https://getstream.io/chat/docs/sdk/react/features/messaging/message-actions/).

### Replacing the bounce prompt

`MessageBouncePrompt` is an override key. A replacement reads the bounced message and the three handlers from `useMessageBounceContext()`, and closes the modal with `useModalContext()`:

```tsx
import type { MessageBouncePromptProps } from "stream-chat-react";
import {
  useMessageBounceContext,
  useModalContext,
  WithComponents,
} from "stream-chat-react";

const BouncePrompt = (_props: MessageBouncePromptProps) => {
  const { handleDelete, handleEdit, message } = useMessageBounceContext();
  const { close } = useModalContext();

  return (
    <div className="bounce-prompt">
      <p>“{message.text}” wasn't sent: it breaks the community guidelines.</p>
      <button
        onClick={(event) => {
          handleEdit(event);
          close();
        }}
        type="button"
      >
        Edit
      </button>
      <button
        onClick={(event) => {
          handleDelete(event);
          close();
        }}
        type="button"
      >
        Delete
      </button>
    </div>
  );
};

export const App = () => (
  <WithComponents overrides={{ MessageBouncePrompt: BouncePrompt }}>
    {/* the rest of the app */}
  </WithComponents>
);
```

`MessageDeletedBubble`, `MessageDeleted`, `EditedMessagePreview`, `MessageEditedIndicator` and `MessageStatus` are override keys too. See [Overriding components](https://getstream.io/chat/docs/sdk/react/ui/components/overriding-components/).

### Editing a message in an edit composer

By default, an edit happens in the composer at the bottom of the channel or thread. To edit a message where it sits, give it a composer of its own: an edit composer, whose composition context is the message. Supply it to a `MessageComposer` with `MessageComposerControllerProvider`:

```tsx
import { useEffect, useState } from "react";
import type { LocalMessage, MessageComposerState } from "stream-chat";
import { MessageComposer as MessageComposerController } from "stream-chat";
import {
  MessageComposer,
  MessageComposerControllerProvider,
  useChatContext,
  useStateStore,
} from "stream-chat-react";

const editingSelector = (state: MessageComposerState) => ({
  isEditing: state.editedMessage !== null,
});

export const InlineEditor = ({
  message,
  onDone,
}: {
  message: LocalMessage;
  onDone: () => void;
}) => {
  const { client } = useChatContext();
  const [composer] = useState(
    () =>
      new MessageComposerController({
        client,
        composition: message,
        compositionContext: message,
      }),
  );
  const { isEditing } = useStateStore(composer.state, editingSelector);

  // Saving or cancelling the edit empties the composer, which ends the edit.
  useEffect(() => {
    if (!isEditing) onDone();
  }, [isEditing, onDone]);

  if (!isEditing) return null;

  return (
    <MessageComposerControllerProvider messageComposerController={composer}>
      <MessageComposer />
    </MessageComposerControllerProvider>
  );
};
```

Render `InlineEditor` in place of the message text from a custom `MessageUI`, and open it from a custom Edit action. The composer the user was typing in stays untouched. See [Message composer UI](https://getstream.io/chat/docs/sdk/react/ui/components/message-composer-ui/#which-composer-it-drives) for how a supplied composer is picked.

## Reference

- State: [Message composer](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-composer/), [Message operations](https://getstream.io/chat/docs/sdk/react/state/writing-messages/message-operations/), [What's optimistic and what isn't](https://getstream.io/chat/docs/sdk/react/state/writing-messages/whats-optimistic-and-what-isnt/), and the [MessageComposer API](https://getstream.io/chat/docs/sdk/react/reference/state/messagecomposer-api/).
- UI: [Message composer UI](https://getstream.io/chat/docs/sdk/react/ui/components/message-composer-ui/) and [Message UI](https://getstream.io/chat/docs/sdk/react/ui/components/message-ui/).
- Props: [MessageComposer](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/messagecomposer/), [Composer UI components](https://getstream.io/chat/docs/sdk/react/component-reference/message-composer/composer-ui-components/), [Message UI components](https://getstream.io/chat/docs/sdk/react/component-reference/message/message-ui-components/), [MessageContext](https://getstream.io/chat/docs/sdk/react/component-reference/message/messagecontext/) and [MessageBounceContext](https://getstream.io/chat/docs/sdk/react/component-reference/message/messagebouncecontext/).
- Hooks: [Composer hooks](https://getstream.io/chat/docs/sdk/react/reference/hooks/composer-hooks/) and [Message hooks](https://getstream.io/chat/docs/sdk/react/reference/hooks/message-hooks/).

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/features/messaging/sending-editing-and-deleting/](https://getstream.io/chat/docs/sdk/react/features/messaging/sending-editing-and-deleting/).