# useUpload (https://s3.dimah.dev/docs/react/hooks/upload)



`useUpload` manages the complete client-side upload lifecycle: file selection, validation against server constraints, presigned URL acquisition, chunked progress tracking, and final server confirmation.

```tsx title="components/avatar-uploader.tsx"
"use client";

import { useUpload } from "@dimah-s3/react";

export function AvatarUploader() {
  const upload = useUpload({
    route: "avatar",
    onSuccess: (results) => {
      console.log("Uploaded object key:", results[0]?.key);
    },
  });

  return (
    <div
      {...upload.getRootProps()}
      className="border border-dashed p-6 rounded-lg text-center cursor-pointer hover:border-primary transition-colors"
    >
      <input {...upload.getInputProps()} />
      {upload.isUploading ? (
        <p className="text-sm font-medium">
          Uploading: {upload.progress.percent}%
        </p>
      ) : (
        <p className="text-sm text-muted-foreground">
          Click or drop avatar here
        </p>
      )}
    </div>
  );
}
```

***

## File selection and intake [#file-selection-and-intake]

`useUpload` provides native bindings for drag-and-drop surfaces and file input elements:

* **`getRootProps()`**: Spread onto your container element to bind drag-and-drop and click-to-browse handlers.
* **`getInputProps()`**: Spread onto a hidden `<input type="file" />`.
* **`open()`**: Programmatically trigger the OS file dialog from a custom button.
* **`handleFiles(files)`**: Manually submit `File`, `File[]`, or `FileList` (e.g. from clipboard paste, webcam capture, or canvas blob).

```tsx
const upload = useUpload({
  route: "avatar",
  noClick: true, // Prevents container click from opening file picker
});

return (
  <div {...upload.getRootProps()}>
    <input {...upload.getInputProps()} />
    <button type="button" onClick={() => upload.open()}>
      Choose File
    </button>
  </div>
);
```

***

## Multi-file batch uploads [#multi-file-batch-uploads]

Set `maxFiles` to allow multiple file selections. You can control concurrency with `concurrentFiles`:

```tsx
const upload = useUpload({
  route: "avatar",
  maxFiles: 5,
  concurrentFiles: 2,
  onFileSuccess: (file, result) => {
    console.log(`Uploaded ${file.name} to ${result.key}`);
  },
  onSuccess: (results) => {
    console.log("All uploads completed:", results);
  },
});
```

***

## Progress and state tracking [#progress-and-state-tracking]

The hook surfaces detailed reactive state for UI rendering:

* **`phase`**: Current upload phase (`idle` | `validating` | `presigning` | `uploading` | `finalizing` | `success` | `error`).
* **`progress`**: Aggregate progress object containing `loaded`, `total`, `percent`, and instantaneous `speed` (bytes/sec).
* **`files`**: Per-file state array containing individual progress, status (`pending`, `uploading`, `success`, `error`), and `previewUrl`.
* **`file`**: Convenience shorthand for `files[0]` when `maxFiles` is 1.
* **`isUploading`**: Boolean flag indicating active byte transfer (`phase === "uploading"`).
* **`isPending`**: Boolean flag indicating in-flight operation from presign to confirmation.

***

## Type reference [#type-reference]

```ts
import type {
  UseUploadOptions,
  UseUploadReturn,
  UploadProgress,
  UploadResult,
  UploadFileState,
} from "@dimah-s3/react";
```

### UseUploadOptions [#useuploadoptions]

<AutoTypeTable path="packages/react/src/hooks/use-upload.ts" name="UseUploadOptions" />

***

### UseUploadReturn [#useuploadreturn]

<AutoTypeTable path="packages/react/src/hooks/use-upload.ts" name="UseUploadReturn" />

## Frequently asked questions [#frequently-asked-questions]

<Accordions>
  <Accordion title="What if the route catalog fails to load?">
    `useUpload` still works. The server enforces type and size on presign.
    `policy.catalogStatus` is `"error"` and `policy.catalogError` is set so
    you can toast or pass explicit `accept` / `maxFileSize` on the hook.
    In development, a one-time console warning is also printed.

    Custom backends that do not implement `GET /routes` should pass those
    constraints on `useUpload` and ignore the catalog status.
  </Accordion>

  <Accordion title="How do I display a local image preview before uploading?">
    Use `previewUrl` on individual items in the `upload.files` array or create an object URL from `upload.file`:

    ```tsx
    export function AvatarWithPreview() {
      const upload = useUpload({ route: "avatar" });
      const preview = upload.file?.previewUrl;

      return (
        <div>
          <div {...upload.getRootProps()}>
            <input {...upload.getInputProps()} />
            {preview ? (
              <img
                src={preview}
                alt="Selected avatar"
                className="size-20 rounded-full object-cover"
              />
            ) : (
              <button type="button">Select avatar</button>
            )}
          </div>
          {upload.isUploading && <span>{upload.progress.percent}%</span>}
        </div>
      );
    }
    ```
  </Accordion>

  <Accordion title="How do I pass custom metadata to the server on upload?">
    Pass static `uploadOptions` or a dynamic `getUploadOptions` function:

    ```tsx
    const upload = useUpload({
      route: "avatar",
      getUploadOptions: (file) => ({
        metadata: {
          originalName: file.name,
          uploadedAt: new Date().toISOString(),
        },
      }),
    });
    ```
  </Accordion>

  <Accordion title="How do I pause, resume, or cancel in-flight uploads?">
    Use `cancel()` to abort the upload and clean up temporary parts, or `detach()` when an `uploadStore` is configured to pause for later resumption:

    ```tsx
    const upload = useUpload({
      route: "avatar",
      uploadStore: localStorageStore,
    });

    // Abort immediately
    upload.cancel();

    // Soft-stop to resume on next visit
    upload.detach();
    ```
  </Accordion>
</Accordions>
