# useUpload (https://v1.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/file-uploader.tsx"
"use client";

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

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

  return (
    <div
      {...upload.getRootProps()}
      className="hover:border-primary cursor-pointer rounded-lg border border-dashed p-6 text-center transition-colors"
    >
      <input {...upload.getInputProps()} />
      {upload.isUploading ? (
        <p className="text-sm font-medium">
          Uploading: {upload.progress.percent}%
        </p>
      ) : (
        <p className="text-muted-foreground text-sm">
          Click or drop files 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 // [!code ++]
});

return (
  <div {...upload.getRootProps()}>
    <input {...upload.getInputProps()} />
    <button type="button" onClick={() => upload.open()}>
      {" "}
      {/* [!code ++] */}
      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, // [!code ++]
  concurrentFiles: 2, // [!code ++]
  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 } 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 happens if the route catalog fails to load?">

`useUpload` still works because 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?">

Read `previewUrl` from an item in `upload.files`; `upload.file?.previewUrl` is
the single-file shorthand:

```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) => ({
    // [!code ++]
    metadata: {
      // [!code ++]
      originalName: file.name, // [!code ++]
      uploadedAt: new Date().toISOString(), // [!code ++]
    }, // [!code ++]
  }), // [!code ++]
});
```

</Accordion>

<Accordion title="How do I pause, resume, or cancel in-flight uploads?">

Call `cancel()` to abort the upload and clean up temporary parts. To stop a
stored multipart upload without aborting it, call `detach()` and resume it on a
later visit:

```tsx
const upload = useUpload({
  route: "avatar",
  uploadStore: localStorageStore, // [!code ++]
});

// Abort immediately
upload.cancel();

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

</Accordion>

</Accordions>
