File lifecycle
Each file is an UploadFile with a status, progress, optional error, and optional adapter response. The UI should render from this object — not from ad-hoc flags.
State machine
pending → uploading → success
→ error → (retryFile) → uploading
→ cancelled → (retryFile) → uploading| Status | Meaning |
|---|---|
pending | Accepted, not yet started. Waiting for upload() or autoUpload. |
uploading | In flight (or queued behind the concurrency cap). |
success | Adapter resolved. response is set. |
error | Adapter rejected after retries exhausted. error is set. |
cancelled | cancelFile / cancelAll / removeFile abort. |
Automatic retries (maxRetries) happen inside the queue while the item is still considered in-flight. The file does not flicker through error between automatic attempts. User-facing retryFile is for files that already landed on error or cancelled.
Starting uploads
autoUpload: true—addFilesenqueues immediately.autoUpload: false(default) — files sit inpendinguntil you callupload().
Use manual mode when the user should review the list, fill a form, or confirm before bytes leave the browser.
const uploader = useUploader({ adapter, autoUpload: false });
<button onClick={uploader.upload} disabled={uploader.isUploading}>
Upload {uploader.files.filter((f) => f.status === 'pending').length} files
</button>Concurrency
concurrency (default 3) is the maximum number of adapters running at once. The rest wait in an in-memory queue.
Raise it for many small files on a fast link. Lower it for large videos or a backend that rate-limits. This is client-side only; it does not replace server quotas.
Retry
Two layers:
- Automatic —
maxRetries+retryDelay. Delay is exponential:retryDelay * 2^attempt. DefaultmaxRetriesis0(fail once). - Manual —
retryFile(id)/retryAll()forerrorandcancelled.
retryCount on UploadFile increments on manual retry. Use it in the UI if you want “retried 2×”.
Do not retry blindly on 4xx. If the adapter can distinguish “payload too large” from “timeout”, throw a typed error and skip auto-retry for the former (maxRetries: 0, let the user fix the file).
Cancel vs remove vs clear
| Method | In-flight request | File stays in files |
|---|---|---|
cancelFile / cancelAll | Aborted | Yes, status cancelled |
removeFile | Aborted | No |
clearCompleted | Untouched | Removes success only |
clearAll | Aborted | Empties the list |
removeFile is the right action for a trash icon. cancelFile is for “stop this upload but keep the row”. clearCompleted is a tidy-up after a batch.
The adapter must listen to signal. If it ignores abort, cancel will update UI status while the request keeps running.
Progress
- Per file:
file.progress(0–100), driven byonProgressin the adapter. - Aggregate:
totalProgressis the average across all files, including completed ones.
If you need “average of active uploads only”, compute it yourself from files.filter(f => f.status === 'uploading'). The built-in value is a simple overall bar.
fetch cannot feed onProgress. Either use XHR or treat progress as indeterminate (isUploading + a spinner).
Callbacks
| Callback | When |
|---|---|
onFileAdded | After a file is accepted into state |
onFileRemoved | After removeFile |
onUploadStart | Adapter is about to run |
onUploadProgress | Each onProgress tick |
onUploadSuccess | Adapter resolved |
onUploadError | Adapter rejected (after auto-retries) |
onAllComplete | Queue drained (no active or waiting items) |
Callbacks are stored in a ref, so you can pass inline functions without resetting the queue. Still keep them cheap; onUploadProgress can fire often.
onAllComplete fires when the queue goes idle — including after cancels that empty the active set. If you need “all succeeded”, check files.every(f => f.status === 'success') inside the callback.
UploadFile shape
interface UploadFile<TResponse> {
id: string;
file: File;
status: 'pending' | 'uploading' | 'success' | 'error' | 'cancelled';
progress: number;
error: Error | null;
response: TResponse | null;
retryCount: number;
}id is generated by the library. Use it as a React key and as the argument to cancel/retry/remove.