# Sending with pending uploads

> **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

By default, the composer won't send a message while one of its attachments is still uploading: the send button stays disabled until every upload finishes. With pending uploads turned on, the user sends right away. The message appears in the list with live upload progress, and the request goes out once the uploads finish.

- **Send while uploading.** The send button works as soon as a file is attached, and the composer clears at once, so the user can write the next message.
- **Progress in the message list.** File, audio and voice recording attachments show a progress ring with the uploaded and total size, such as "12.3 MB / 40.0 MB". Each image and video shows a progress ring over its thumbnail. While the size of the transfer isn't known, a spinner shows instead of the ring.
- **Local preview.** Images, videos and audio render from the user's own file until the upload finishes.
- **Cancel.** Before sending, removing an attachment from the composer's preview cancels its upload.

Turn it on when users attach large files on slow connections, where waiting for an upload before writing the next message is what frustrates them. Leave it off if your app needs a message to be on the server the moment it appears in the list.

## How it works across the layers

The user attaches a 40 MB video to a message in "Design", types "Here's the cut", and presses send while the upload is at 30%:

1. **The composer lets them send.** With `pendingUploadsEnabled` on, the composer counts an attachment that is still uploading, or whose upload failed, as content. `composer.allowsPendingUploads` reports the setting, and the send button reads it through `useMessageComposerHasSendableData()`.
2. **The composer clears.** `MessageComposer.send()` builds the message and clears the composer before the request, as it always does. The video's upload keeps running.
3. **The message appears at once.** `channel.messageOperations.send()` adds the message with status `sending`. Its attachment still carries the local file, so the list renders the video from it.
4. **The request waits for the upload.** Message operations wait for the message's uploads to finish, then add the uploaded URLs to the message and send it.
5. **Messages keep their order.** While pending uploads are on, the sends of a channel and its threads run one after another. A short text sent right after the video can't arrive first. Edits and deletes don't wait.

If any upload of the message fails, the whole message fails: no request is made, and the message is marked `failed`. **Resend** uploads only the files that didn't make it. Uploads that succeeded keep their URLs.

An attachment that the server's upload settings rejected before its upload started (`blocked`) doesn't count. A message whose only attachment was blocked can't be sent.

The upload states and the composer's attachment part are explained on [Message composer](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-composer/), and failed sends on [Message operations](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-operations/).

### Progress in the UI

The attachments in the message list show the upload's progress. The composer's upload previews show progress and a retry button only while pending uploads are off: with them on, a preview shows neither while its file uploads, and the progress shows on the message once it's sent. In the message list:

- `AttachmentUploadIndicator` draws the progress ring, or a spinner while the progress isn't known. With `variant="overlay"` it covers an image or video thumbnail through `MediaUploadProgressOverlay`.
- File, audio and voice recording attachments put the uploaded and total size next to the ring.

The indicators read `client.uploadManager` with `usePendingAttachmentUpload(localId)`, keyed by the attachment's local id. The upload manager holds a record only while a request runs, so an indicator shows nothing for an attachment the server returned, and stops by itself when the upload is canceled. An upload that ends after reaching 90% stays at 100% for 350 milliseconds before the indicator goes away, so the ring doesn't vanish halfway.

While pending uploads are on, the message list's `NotificationList` leaves out the notifications of canceled uploads.

`messageComposer.attachments.trackUploadProgress` turns the progress reports on, and is on by default. With it off, the indicators show a spinner for the whole upload.

### Limitations

A message whose attachments haven't finished uploading isn't kept as a pending task for the next reconnect: when the upload fails, the message is marked `failed`, and the user resends it. See [Offline support](https://getstream.io/chat/docs/sdk/react-native/v10/state/offline-support/offline-support/).

## Turning it on or off

Pending uploads are off by default. Turn them on with `messageComposer.attachments.pendingUploadsEnabled` in [`client.config`](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/configuration/):

```ts
client.config.set({
  messageComposer: { attachments: { pendingUploadsEnabled: true } },
});
```

`Chat` with `enableOfflineSupport` turns them on for you, because offline support needs the local message to keep its pending attachments. It writes the value only when none is registered, so setting `pendingUploadsEnabled: false` yourself keeps them off.

The setting is client only. It applies to composers that already exist and to those created later, in channels, threads and edits alike. To turn it on for one composer instead, call `MessageComposer.updateConfig({ attachments: { pendingUploadsEnabled: true } })`. Inside a `Channel` or `Thread`, `usePendingUploadsEnabled()` returns the value for the current composer.

Uploads themselves must be allowed: see [Attachments and uploads](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/attachments-and-uploads/).

## Customizing

### Replacing the progress UI

`AttachmentUploadIndicator` is an override key. It receives the attachment's `localId`, and reads the upload with `usePendingAttachmentUpload(localId)`:

```tsx
import { StyleSheet, Text, View } from "react-native";
import type { AttachmentUploadIndicatorProps } from "stream-chat-react-native";
import {
  usePendingAttachmentUpload,
  WithComponents,
} from "stream-chat-react-native";

const UploadPercent = ({
  localId,
  variant,
}: AttachmentUploadIndicatorProps) => {
  const { isUploading, uploadProgress } = usePendingAttachmentUpload(localId);

  if (!isUploading) return null;

  return (
    <View
      pointerEvents="none"
      style={
        variant === "overlay"
          ? [StyleSheet.absoluteFill, styles.center]
          : undefined
      }
    >
      <Text>
        {uploadProgress === undefined
          ? "Uploading..."
          : `${Math.round(uploadProgress)}%`}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  center: {
    alignItems: "center",
    justifyContent: "center",
  },
});

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

The indicator's props:

| Prop             | Description                                                                                | Type                     |
| ---------------- | ------------------------------------------------------------------------------------------ | ------------------------ |
| `localId`        | The attachment's local id, which keys its upload in `client.uploadManager`.                | `string`                 |
| `sourceUrl`      | The attachment's URL. The default indicator renders only while it is a local file.         | `string`                 |
| `variant`        | `"compact"` sits next to a file's details; `"overlay"` covers an image or video thumbnail. | `"compact" \| "overlay"` |
| `size`           | The diameter of the ring.                                                                  | `number`                 |
| `strokeWidth`    | The width of the ring.                                                                     | `number`                 |
| `style`          | Style of the ring.                                                                         | `StyleProp<ViewStyle>`   |
| `containerStyle` | Style of the container in the compact variant.                                             | `StyleProp<ViewStyle>`   |

`MediaUploadProgressOverlay` and `CircularProgressIndicator` are override keys too, to change only the overlay or only the ring.

### Reading the upload state

`usePendingAttachmentUpload(localId)` returns two values:

| Value            | Type                  | Meaning                                                                               |
| ---------------- | --------------------- | ------------------------------------------------------------------------------------- |
| `isUploading`    | `boolean`             | A request for this attachment is running now. Render progress only while it's `true`. |
| `uploadProgress` | `number \| undefined` | `0` to `100` while the size of the transfer is known, `undefined` otherwise.          |

A message attachment carries its local id in `attachment.localMetadata.id` until the server returns the message.

### Telling users what a pending message means

A message that is still uploading isn't on the server yet. The default UI shows this through the progress on the attachment and the clock in the message footer. A custom attachment or message UI should keep that signal, and should handle the failed state, where the user has to resend.

## 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/) and the [configuration tree](https://getstream.io/chat/docs/sdk/react-native/v10/reference/state/configuration-tree/).
- UI: [Message composer UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/message-composer-ui/) and [Overriding components](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/overriding-components/).
- Props: [Attachments](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message/attachments/) and [Composer UI components](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/composer-ui-components/).
- Hooks: [Composer hooks](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/composer-hooks/).

---

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