Uplofile is open sourceStar on GitHub

Batch Upload

Collect multiple files and upload them all at once in a single request.

Preview

Add files, then upload them together as one batch.

Code

1import { useCallback, useState } from "react";
2import {
3 UplofilePreview,
4 UplofileRoot,
5 UplofileTrigger,
6 type UploadFileItem,
7 type UploadStatus,
8} from "@/components/ui/uplofile";
9import { mockUpload } from "@/lib/utils.ts";
10
11type BatchStatus = Extract<UploadStatus, "idle" | "uploading" | "done" | "error">;
12
13const queueFile = async (file: File) => ({
14 id: file.name,
15 url: URL.createObjectURL(file),
16});
17
18export default function BatchUploadDemo() {
19 const [status, setStatus] = useState<BatchStatus>("idle");
20 const [progress, setProgress] = useState(0);
21
22 const uploadBatch = useCallback(async (files: File[]) => {
23 if (files.length === 0) return;
24
25 setStatus("uploading");
26 setProgress(0);
27
28 try {
29 await mockBatchUpload(files, setProgress);
30 setStatus("done");
31 } catch {
32 setStatus("error");
33 }
34 }, []);
35
36 return (
37 <UplofileRoot upload={queueFile} multiple accept="*/*">
38 <UplofilePreview
39 render={({ items }) => {
40 const files = items.flatMap((item) => (item.file ? [item.file] : []));
41
42 return (
43 <div className="space-y-4">
44 <div className="rounded-xl border border-border bg-muted/30 p-4">
45 <div className="flex flex-wrap gap-2">
46 <UplofileTrigger asChild>
47 <button className="rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium">
48 Add files
49 </button>
50 </UplofileTrigger>
51
52 <button
53 type="button"
54 onClick={() => uploadBatch(files)}
55 disabled={files.length === 0 || status === "uploading"}
56 className="rounded-lg bg-primary px-3 py-2 text-sm font-semibold text-primary-foreground disabled:opacity-50"
57 >
58 Upload batch{files.length ? ` (${files.length})` : ""}
59 </button>
60 </div>
61
62 {status !== "idle" && (
63 <div className="mt-4 space-y-2">
64 <div className="flex justify-between text-xs text-muted-foreground">
65 <span>{status}</span>
66 <span>{progress}%</span>
67 </div>
68 <div className="h-2 overflow-hidden rounded-full bg-secondary">
69 <div
70 className="h-full bg-primary transition-all"
71 style={{ width: `${progress}%` }}
72 />
73 </div>
74 </div>
75 )}
76 </div>
77
78 <div className="divide-y overflow-hidden rounded-xl border border-border bg-card">
79 {items.length === 0 ? (
80 <p className="p-8 text-center text-sm text-muted-foreground">
81 Add files, then upload them together as one batch.
82 </p>
83 ) : (
84 items.map((item) => <BatchFileItem key={item.uid} item={item} />)
85 )}
86 </div>
87 </div>
88 );
89 }}
90 />
91 </UplofileRoot>
92 );
93}
94
95async function mockBatchUpload(
96 files: File[],
97 setProgress: (progress: number) => void,
98) {
99 await mockUpload(files[0], undefined, setProgress);
100
101 return files.map((file) => ({
102 id: file.name,
103 url: `https://example.com/uploads/${encodeURIComponent(file.name)}`,
104 }));
105}
106
107function BatchFileItem({ item }: { item: UploadFileItem }) {
108 return (
109 <div className="p-3">
110 <p className="truncate text-sm font-medium">{item.name}</p>
111 </div>
112 );
113}

Key Points

  • The upload function defers resolution until a batch trigger resolves all pending promises.
  • A Upload All button drains the queue and sends all files as one batch.
  • useUplofile is not needed — pending state is tracked outside the library via a ref.
  • Abort handling works normally: cancelling a pending file removes it from the batch queue.