# Sending, editing and deleting

> **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/).

## 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/v15/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 one of three results:

- `'sent'`: the message went out.
- `'nothing-to-send'`: there was nothing to send, or a middleware stopped the send. The composer keeps its contents.
- `'failed'`: the request failed. The composer is already empty, and the message is in the list as `failed`.

The statuses and the request handlers are explained on [Message operations](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-operations/), and what the composer does on [Message composer](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/).

### 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 failed send is kept for 5 minutes (up to 100 messages), so a retry sends exactly what failed. After that, the retry rebuilds the request from the message. A message that failed with a 403 error can't be resent.

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

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 three 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/v15/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. The default action shows a "Message deleted" notification either way.

`MessageOperations.delete()` takes two options:

- `hard: true` removes the message from the list instead of marking it deleted.
- `delete_for_me: true` deletes it for the current user only.

What each operation does before and after the server answers is on [What's optimistic and what isn't](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/whats-optimistic-and-what-isnt/#message-operations).

### 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. Polls and Giphy messages can't be edited. A message that is still sending has no actions.

A failed message has a short menu:

- **Resend**, when the user has `send-message` and the error wasn't a 403.
- **Edit Message** and **Delete message**, when the request never reached the server.

A bounced message has no menu at all: its choices are in the bounce prompt.

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/v15/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/v15/ui/customizing/actions/#message-actions) and [Message actions](https://getstream.io/chat/docs/sdk/react/v15/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/v15/ui/customizing/overriding-components/).

### Editing a message in place

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

---

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