# React (/docs/react)



`s3delivery/react` gives you upload hooks with rich, predictable state, plus two
drop-in components. `react` is an optional peer dependency (`>=18`).

```tsx
import { useUploadFiles } from "s3delivery/react";
```

All hooks build on [`uploadFiles`](/docs/browser-uploads): parallel files,
automatic multipart for large files, client-side
[constraints](/docs/browser-uploads#constraints), retry with backoff, and an
abortable upload. They talk to your [route handler](/docs/route-handler), so your
key stays on the server.

<Callout title="Which hook?">
  Use [`useUploadFile`](#useuploadfile) for a single file and
  [`useUploadFiles`](#useuploadfiles) for many — they are the recommended hooks.
  [`useUploader`](#useuploader) is an older, leaner hook kept for compatibility;
  `<UploadButton>` / `<UploadDropzone>` are built on it.
</Callout>

## `useUploadFiles` [#useuploadfiles]

Upload multiple files with full state.

```tsx
import { useUploadFiles } from "s3delivery/react";

function Gallery() {
  const { upload, uploadedFiles, progress, isPending, error } = useUploadFiles({
    maxFiles: 10,
    maxFileSize: 50 * 1024 * 1024,
    allowedFileTypes: ["image/*", "application/pdf"],
    onUploadComplete: (files) => console.log(files),
  });

  return (
    <>
      <input type="file" multiple onChange={(e) => upload(e.target.files!)} />
      {isPending && <progress value={progress} max={100} />}
      {error && <p role="alert">{error.message}</p>}
      {uploadedFiles.map((f) => (
        <a key={f.id} href={f.url}>
          {f.name}
        </a>
      ))}
    </>
  );
}
```

### Return shape [#return-shape]

| Field           | Type                                             | Notes                                                       |
| --------------- | ------------------------------------------------ | ----------------------------------------------------------- |
| `upload`        | `(files, options?) => Promise<S3DeliveryFile[]>` | Start an upload. `options` can override `visibility`.       |
| `uploadedFiles` | `S3DeliveryFile[]`                               | The files that uploaded successfully (set on success).      |
| `progress`      | `number`                                         | Aggregate progress across all files, 0–100.                 |
| `progresses`    | `{ file: File; progress: UploadProgress }[]`     | Per-file progress for the in-flight batch.                  |
| `isPending`     | `boolean`                                        | An upload is currently running.                             |
| `isSuccess`     | `boolean`                                        | The last upload finished successfully.                      |
| `isError`       | `boolean`                                        | The last upload failed.                                     |
| `isAborted`     | `boolean`                                        | The last upload was aborted.                                |
| `isSettled`     | `boolean`                                        | The last upload finished (success or error).                |
| `error`         | `S3DeliveryError \| null`                        | The error from the last failed upload.                      |
| `reset`         | `() => void`                                     | Reset all state to idle (also aborts any in-flight upload). |
| `control`       | `{ abort: () => void; reset: () => void }`       | Imperative handle.                                          |

### Options [#options]

`useUploadFiles` accepts every [`uploadFiles` option](/docs/browser-uploads#options)
except `signal`, `onProgress`, `onFileProgress`, and `onFileComplete` (the hook
manages those internally), plus lifecycle callbacks:

| Option             | Type                                 | Notes                                              |
| ------------------ | ------------------------------------ | -------------------------------------------------- |
| `onBeforeUpload`   | `(files: File[]) => void \| Promise` | Runs before any URL is requested. Throw to reject. |
| `onProgress`       | `(percent: number) => void`          | Aggregate progress on every tick.                  |
| `onUploadComplete` | `(files: S3DeliveryFile[]) => void`  | Runs once when every file succeeds.                |
| `onError`          | `(error: S3DeliveryError) => void`   | Runs on any failure (validation, network, abort).  |
| `onSettled`        | `() => void`                         | Always runs after success or failure.              |

### Per-file progress [#per-file-progress]

```tsx
const { upload, progresses } = useUploadFiles();

<ul>
  {progresses.map(({ file, progress }) => (
    <li key={file.name}>
      {file.name} — {progress.percent}%
    </li>
  ))}
</ul>;
```

## `useUploadFile` [#useuploadfile]

The same engine, narrowed to a single file. It defaults `maxFiles` to `1` and
exposes `uploadedFile` (singular).

```tsx
import { useUploadFile } from "s3delivery/react";

function Avatar() {
  const { upload, uploadedFile, progress, isPending, control } = useUploadFile({
    maxFileSize: 5 * 1024 * 1024,
    allowedFileTypes: ["image/*"],
  });

  return (
    <>
      <input type="file" onChange={(e) => upload(e.target.files![0])} />
      {isPending && (
        <>
          <progress value={progress} max={100} />
          <button onClick={control.abort}>Cancel</button>
        </>
      )}
      {uploadedFile && <img src={uploadedFile.url} alt="" />}
    </>
  );
}
```

The return shape matches `useUploadFiles` with `uploadedFile: S3DeliveryFile | null`
in place of `uploadedFiles` (and no `progresses`). The `upload` function takes a
single `File` and resolves with a single `S3DeliveryFile`.

## Aborting and resetting [#aborting-and-resetting]

`control.abort()` cancels an in-flight upload — parts and pending retries stop
immediately, `error.code` is `ABORTED`, and `isAborted` becomes `true`.
`control.reset()` (or `reset()`) clears all state back to idle and also aborts any
upload still running.

```tsx
const { upload, control, isAborted, reset } = useUploadFile();

<button onClick={control.abort}>Cancel</button>;
{
  isAborted && <button onClick={reset}>Try again</button>;
}
```

## Per-call visibility [#per-call-visibility]

Override visibility for a single upload without re-configuring the hook:

```tsx
const { upload } = useUploadFiles({ visibility: "private" });

await upload(files, { visibility: "public" }); // public, just this time
```

## `useUploader` [#useuploader]

The older headless hook. Leaner state; wrap your own UI around it. Prefer
`useUploadFile` / `useUploadFiles` for new code.

```tsx
import { useUploader } from "s3delivery/react";

const { upload, isUploading, progress, uploaded, error, reset } = useUploader({
  onSuccess: (files) => console.log(files),
  onError: (e) => console.error(e),
});
```

| Field         | Type                                   |
| ------------- | -------------------------------------- |
| `upload`      | `(files) => Promise<S3DeliveryFile[]>` |
| `isUploading` | `boolean`                              |
| `progress`    | `number` (0–100)                       |
| `uploaded`    | `S3DeliveryFile[]`                     |
| `error`       | `Error \| null`                        |
| `reset`       | `() => void`                           |

It accepts all [`uploadFiles` options](/docs/browser-uploads#options) plus
`onSuccess` and `onError`.

## Prebuilt components [#prebuilt-components]

`<UploadButton>` and `<UploadDropzone>` are zero-config drop-ins built on
`useUploader`. They handle the hidden file input, drag-and-drop, and an
uploading/progress label.

```tsx
import { UploadButton, UploadDropzone } from "s3delivery/react";

<UploadButton accept="image/*" onSuccess={(files) => console.log(files)} />;

<UploadDropzone
  accept="image/*,application/pdf"
  maxFileSize={10 * 1024 * 1024}
  onSuccess={(files) => console.log(files)}
/>;
```

Both accept every `useUploader` option plus:

| Prop        | Default | Notes                                            |
| ----------- | ------- | ------------------------------------------------ |
| `accept`    | —       | Native file-input `accept` filter.               |
| `multiple`  | `true`  | Allow selecting more than one file.              |
| `disabled`  | `false` | Disable the control.                             |
| `className` | —       | Replace the default inline styles with your own. |
| `style`     | —       | Merge extra inline styles.                       |
| `children`  | —       | Custom label / dropzone content.                 |

Pass `className` to opt out of the built-in inline styles and style the control
yourself.
