React
Hooks and prebuilt components from s3delivery/react.
s3delivery/react gives you upload hooks with rich, predictable state, plus two
drop-in components. react is an optional peer dependency (>=18).
import { useUploadFiles } from "s3delivery/react";All hooks build on uploadFiles: parallel files,
automatic multipart for large files, client-side
constraints, retry with backoff, and an
abortable upload. They talk to your route handler, so your
key stays on the server.
Which hook?
Use useUploadFile for a single file and
useUploadFiles for many — they are the recommended hooks.
useUploader is an older, leaner hook kept for compatibility;
<UploadButton> / <UploadDropzone> are built on it.
useUploadFiles
Upload multiple files with full state.
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
| 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
useUploadFiles accepts every uploadFiles option
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
const { upload, progresses } = useUploadFiles();
<ul>
{progresses.map(({ file, progress }) => (
<li key={file.name}>
{file.name} — {progress.percent}%
</li>
))}
</ul>;useUploadFile
The same engine, narrowed to a single file. It defaults maxFiles to 1 and
exposes uploadedFile (singular).
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
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.
const { upload, control, isAborted, reset } = useUploadFile();
<button onClick={control.abort}>Cancel</button>;
{
isAborted && <button onClick={reset}>Try again</button>;
}Per-call visibility
Override visibility for a single upload without re-configuring the hook:
const { upload } = useUploadFiles({ visibility: "private" });
await upload(files, { visibility: "public" }); // public, just this timeuseUploader
The older headless hook. Leaner state; wrap your own UI around it. Prefer
useUploadFile / useUploadFiles for new code.
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 plus
onSuccess and onError.
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.
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.