useMultipleUpload

Overview

The useMultipleUpload hook extends the power of the core upload engine to handle batch uploads. It maintains the identical retry logic and stability of useUpload while introducing powerful concurrency limits. This prevents browser thread throttling and avoids overloading your backend with 50+ concurrent presigned URL API requests.

The hook tracks individual file progress, statuses, and provides a unified overallProgress value for your UI.


Basic Usage

import React, { useState } from "react";
import { useMultipleUpload } from "upload-with-progress";
 
export function MultiFileUploader() {
  const [filesToUpload, setFilesToUpload] = useState<File[]>([]);
 
  const { upload, jobs, overallProgress, isUploadingAll, abortAll } = useMultipleUpload({
    maxFileSize: 50 * 1024 * 1024, // 50MB per file
    allowedTypes: ["image/*", "video/*"],
    concurrency: 3, // Upload max 3 files at a time to prevent bottlenecking
    retries: 3,
  });
 
  const handleStartUpload = async () => {
    if (!filesToUpload.length) return;
 
    // The results array maps 1:1 with the files array passed in.
    const results = await upload(filesToUpload, async (file, index) => {
      // Fetch the presigned URL for the specific file
      const res = await fetch(`/api/presign?name=${encodeURIComponent(file.name)}`);
      return res.json();
    });
    
    const succeeded = results.filter((r) => r.status === "fulfilled");
    console.log(`${succeeded.length}/${results.length} files uploaded successfully.`);
  };
 
  return (
    <div>
      <input 
        type="file" 
        multiple 
        onChange={(e) => {
          if (e.target.files) setFilesToUpload(Array.from(e.target.files));
        }} 
      />
      
      <button onClick={handleStartUpload} disabled={isUploadingAll || !filesToUpload.length}>
        Upload All
      </button>
      
      {isUploadingAll && (
        <div className="batch-progress">
          <h4>Overall Progress: {overallProgress}%</h4>
          <button onClick={abortAll}>Cancel All</button>
        </div>
      )}
 
      <ul>
        {jobs.map((job) => (
          <li key={job.id}>
            <strong>{job.file.name}</strong>
            {job.isUploading && ` - Uploading: ${job.progress}%`}
            {job.error && <span className="error"> - Failed: {job.error}</span>}
            {job.progress === 100 && !job.error && " - ✅ Done"}
          </li>
        ))}
      </ul>
    </div>
  );
}

Hook Options

useMultipleUpload(options) accepts the same configuration object as useUpload, plus concurrency:

OptionTypeDefaultDescription
concurrencynumberInfinityNew. Maximum number of concurrent uploads. Excess files are queued.
maxFileSizenumberInfinityMax size per file in bytes.
allowedTypesstring[]undefinedAllowed MIME types.
timeoutnumber0Per-upload timeout in milliseconds.
retriesnumber0Auto-retries for transient failures.
retryDelaynumber1000Base delay for exponential backoff in milliseconds.
headersRecord<string, string>undefinedCustom HTTP headers sent during the PUT request.
signalAbortSignalundefinedGlobal AbortSignal to cancel the entire batch upload externally.

Returned State

The hook returns an object with the following properties:

PropertyTypeDescription
uploadFunctionTriggers the batch upload. Takes an array of File objects and the getUploadUrl callback.
jobsUploadJob[]The state of all processed files. Updates continuously during the upload.
overallProgressnumberA weighted progress average (loadedBytes / totalBytes) * 100. Ranges from 0 to 100.
isUploadingAllbooleantrue if any file in the batch is currently uploading.
abort(id: string) => voidAbort a single file upload using its job.id.
abortAll() => voidCancels all currently in-flight uploads belonging to the active batch.
reset() => voidCancels in-flight requests and clears the jobs state completely.

The jobs Array

The jobs state array provides detailed information about every file being processed.

interface UploadJob<TMeta> {
  id: string; // Internal UUID generated for the job
  file: File;
  progress: number; // 0 - 100
  loaded: number; // Bytes uploaded
  total: number; // Total bytes in the file
  isUploading: boolean;
  error: string | null;
  meta?: TMeta; // Backend metadata once successful
}