# Sending, editing and deleting

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

## 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 turns into a check when the server confirms it. Two checks mean it was delivered, and two checks in the accent color mean someone read it.
- **Failed messages.** A message whose request failed stays in the list with an error icon. Tapping it opens the message menu, which offers **Resend**.
- **Editing.** **Edit Message** in the message menu shows an "Edit Message" preview above the input and a checkmark in place of the send button. The close button on the preview cancels the edit. Whatever the user was typing before comes back to the input when the edit ends, because the edit happens in a composer of its own. An edited message shows "Edited" in its footer.
- **Deleting.** **Delete Message** asks for confirmation in a native alert, then shows "Message deleted" in place of the message and a "Message deleted" notification.
- **Bounced messages.** A message that moderation bounced shows in an error style. Tapping it opens an alert to send it anyway, edit it, or delete it.

The message menu opens on a long press. See [Message actions](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/message-actions/).

<table>
  <tr>
  <td align='center' width='50%'>

![The composer while editing a message, with an Edit Message preview above the input and a checkmark in place of the send button on iOS](https://getstream.io/docs-assets/images/e85641575c0a.png)

  </td>
  <td align='center' width='50%'>

![The composer while editing a message, with an Edit Message preview above the input and a checkmark in place of the send button on Android](https://getstream.io/docs-assets/images/0a71d6057749.png)

  </td>
  </tr>
</table>

## 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 the send button:

1. The `MessageComposer` component calls `sendMessage` from `MessageInputContext`, which calls the state layer's `MessageComposer.send()` on the composer that `useMessageComposer()` returns: `channel.messageComposer`, or `thread.messageComposer` inside a `Thread`.
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()` doesn't throw when the request fails. It resolves to one of three results (a composition middleware that throws still rejects it):

- `'sent'`: the message went out.
- `'nothing-to-send'`: there was nothing to send, or a middleware stopped the send. `MessageComposer` puts the text back into the input.
- `'failed'`: the request failed. The composer is already empty, and the message is in the list as `failed`.

When the user lacks the `send-links` capability and the text contains a link, `sendMessage` shows a "Links are disabled" alert and sends nothing.

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

### Resending

**Resend** calls `retrySendMessage` from `useMessageOperations()`, which calls `MessageOperations.retry({ localMessage })` on the open thread, or on the channel outside a 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 failed edit also offers **Resend**. It sends the message again rather than repeating the edit, so to retry an edit, edit the message again.

### Editing

**Edit Message** calls `setEditingState(message)` from `useMessageComposerAPIContext()`. The provider that `Channel` and `Thread` render creates an edit composer for the message, or reuses the one in `client.messageComposerCache`, and starts it from the message. While the edit lasts, `useMessageComposer()` returns the edit composer, so the input, the preview and the buttons work on it. The channel's or thread's own composer keeps what the user had typed.

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()` doesn't throw when the request fails, and resolves to the same three results. A bounced message is the exception: saving its edit sends it again as a new message, with `MessageComposer.send()`.

Saving the edit empties the edit composer, which ends the edit. The close button on the preview calls `clearEditingState()`, which ends it without saving. Either way, the input shows the channel's or thread's composer again. See [Message composer](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-composer/) for the composer's side.

### Deleting

**Delete Message** opens a confirmation alert. Confirming calls `deleteMessage(message)` from `useMessageOperations()`, which calls `MessageOperations.delete({ localMessage, options })` on the thread when the message is a reply loaded there, and on the channel otherwise. The message shows as deleted at once. If the server rejects the request, the message comes back and an error notification shows.

A message whose send failed exists only on the device, so deleting it removes it from the list without a request.

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

The default menu deletes for everyone. The action set also has a `deleteForMeMessage` action, which the default list leaves out; see [Message actions](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/message-actions/#customizing).

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-native/v10/state/writing-messages/whats-optimistic-and-what-isnt/).

### 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. Tapping or long-pressing it renders `MessageBounce`, which opens an alert with three choices:

- **Send Anyway** resends it with `MessageOperations.retry()`.
- **Edit Message** starts an edit, as the Edit action does.
- **Delete Message** removes the message from the message lists and the offline database, without a request.

### What renders each part

| Part                                      | Component                                                   | Reads                                                       |
| ----------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
| The input and the send button             | `MessageComposer`, `SendButton`                             | the channel's, thread's or edit composer                    |
| The preview above the input while editing | `Reply`, with `mode='edit'`                                 | `composer.editedMessage`                                    |
| Sending, sent, delivered and read icons   | `MessageStatus`, in `MessageFooter`                         | the message's `status` and `channel.messageReceiptsTracker` |
| "Edited"                                  | `MessageFooter`                                             | `message.message_text_updated_at`                           |
| The error icon                            | `MessageError`                                              | `message.type === 'error'` or status `failed`               |
| A deleted message                         | `MessageDeleted`                                            | `message.type === 'deleted'`                                |
| The bounce alert                          | `MessageBounce`                                             | the message from `useMessageContext()`                      |
| Edit, Delete and Resend                   | `MessageActionList`, with the actions from `messageActions` | the user's capabilities and the message state               |

`MessageFooter` shows the timestamp, the status and "Edited" on the last message of each group, and on any message that hasn't reached the server yet. The status shows on the user's own messages.

## 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 messages with a Giphy or Imgur attachment can't be edited.

A failed message has a short menu:

- **Resend**, on the user's own message;
- **Edit Message** and **Delete Message**, when the user has the capability.

A bounced message has no menu: its choices are in the bounce alert.

Without `send-message`, for example in a frozen channel, `MessageComposer` renders `SendMessageDisallowedIndicator` ("You can't send messages in this channel") instead of the input, unless a message is being edited. To show something else, replace `SendMessageDisallowedIndicator` through `WithComponents`.

To take actions away from users who have the capability, pass `overrideOwnCapabilities` to `Channel` and `Thread`, for example `overrideOwnCapabilities={{ deleteOwnMessage: false }}`. It changes what the UI offers, not what the server allows.

## 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 [Message operations](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-operations/).

### Changing the actions

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

To run your own code when an action runs, pass `handleEdit`, `handleDelete`, `handleDeleteForMe` or `handleRetry` to `Channel` and `Thread`. They're called with the message before the default action, and don't replace it.

### Replacing the bounce alert

`MessageBounce` is an override key. It renders while the bounce choices are open, and receives `setIsBounceDialogOpen` to close them. This one offers editing and deleting only:

```tsx
import { useEffect } from "react";
import { Alert } from "react-native";
import type { MessageBounceProps } from "stream-chat-react-native";
import {
  useMessageComposerAPIContext,
  useMessageContext,
  useMessageOperations,
  WithComponents,
} from "stream-chat-react-native";

const BounceAlert = ({ setIsBounceDialogOpen }: MessageBounceProps) => {
  const { message } = useMessageContext();
  const { removeMessage } = useMessageOperations();
  const { setEditingState } = useMessageComposerAPIContext();

  useEffect(() => {
    Alert.alert(
      "Message not sent",
      "It breaks the community guidelines.",
      [
        {
          onPress: () => {
            setEditingState(message);
            setIsBounceDialogOpen(false);
          },
          text: "Edit",
        },
        {
          onPress: async () => {
            await removeMessage(message);
            setIsBounceDialogOpen(false);
          },
          style: "destructive",
          text: "Delete",
        },
      ],
      { cancelable: true, onDismiss: () => setIsBounceDialogOpen(false) },
    );
  }, [message, removeMessage, setEditingState, setIsBounceDialogOpen]);

  return null;
};

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

Call `setIsBounceDialogOpen(false)` on every way out, including a dismissed alert, so the next tap opens the choices again.

`MessageDeleted`, `MessageError`, `MessageFooter`, `MessageStatus`, `Reply` and `SendButton` are override keys too. See [Overriding components](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/overriding-components/).

## Reference

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

---

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