Show ZIP Progress and Let Users Cancel or Retry
Build a real download experience around a ZIP job — live progress from startZip(), a cancel button via abort(), and a retry that handles failed and partial outcomes correctly.
A ZIP job that takes more than a second needs three affordances: progress
the user can see, a cancel that actually stops work, and a retry that
behaves sensibly when only part of the job failed. This guide builds all
three — in React (where the tray gives you most of it) and in plain
JavaScript with startZip().
The complete example
React applications get progress, cancel, and retry from the built-in
interface: render <EazipTray /> once near the root, start jobs with
useEazip(), and the tray presents the running task — including retry
when a failed job's canRetry is true:
import { useEazip } from '@eazip/react';
export function ExportButton({ files }: { files: { url: string; filename: string }[] }) {
const zip = useEazip();
return (
<button
disabled={zip.isBusy}
onClick={() => zip.download({ files, zipName: 'export.zip' })}
>
Download all
</button>
);
}Custom design system? Drive your own components from the same task state — the full pattern (progress, cancel, retry, per-part downloads, ARIA announcements) is in Headless usage, with exact task fields in the React Reference.
In plain JavaScript, startZip() returns an observable job instead of a
promise-only call:
import { startZip } from '@eazip/core';
const progressBar = document.querySelector<HTMLProgressElement>('#progress')!;
const cancelButton = document.querySelector<HTMLButtonElement>('#cancel')!;
export function runExport(files: { url: string; filename: string }[]) {
const job = startZip({ files, zipName: 'export.zip' });
const unsubscribe = job.subscribe(() => {
const snapshot = job.getSnapshot();
if (snapshot.progress) {
progressBar.max = snapshot.progress.totalFiles ?? 0;
progressBar.value = snapshot.progress.completedFiles ?? 0;
}
});
cancelButton.onclick = () => job.abort();
job.done
.then((result) => result.download())
.catch((error) => showRetryUi(error, () => runExport(files)))
.finally(() => unsubscribe());
}The pieces: subscribe() fires on every snapshot change, getSnapshot()
returns the current immutable state (status, progress, zips,
errors), abort() stops Local work, and done resolves with usable
output or rejects with the terminal error.
Getting each affordance right
Progress: the snapshot's progress reports Local file and byte
counts (it is null for Cloud jobs — show an indeterminate state there,
since the work runs server-side). Prefer "12 of 40 files" over a bare
percentage; it stays honest when file sizes vary wildly.
Cancel: job.abort() (or an AbortSignal passed as signal in the
options, if you already manage one) stops the work — status becomes
aborted, and done rejects. Cancelling is a normal outcome, not an
error to toast about.
Retry — the subtle one. Two different situations wear the retry label:
failed— no usable output. Retry by starting a new job with the same inputs, as in the example. If failures came from expired signed URLs, re-fetch fresh URLs from your endpoint first.partial— usable output with skipped files. Don't silently restart everything: offer the download plus a "N files skipped" notice, and if you retry, retry only the entries listed inerrors. See Partial results and errors.
FAQ
Why does my progress bar jump straight to done for small exports?
Small Local jobs finish in milliseconds. Consider showing progress UI only
after ~300 ms of processing — instant completion needs no ceremony.
Does cancel stop in-flight network requests?
abort() stops the Local pipeline, including its fetching; partially
fetched sources are discarded. For Cloud jobs it stops polling in the
page — see the job model in ZIP jobs.
Can I show progress for a Cloud job?
Snapshot progress is null for Cloud — show an indeterminate spinner
with the job status instead. The tray already does exactly this.
Where do per-part downloads fit?
Split output (maxZipSizeBytes) lands in the snapshot's zips array —
render one action per entry, or call downloadAll(). See
Multi-ZIP splitting.
Let Users Download Selected Files as a ZIP
Wire checkboxes to a ZIP download — track a selection set, build the files array from it, and disable the button when nothing is selected.
ZIP Large Files in the Browser Without Running Out of Memory
Why big browser ZIPs crash tabs, the mitigations in the order to try them — store-only compression, split output, bounded concurrency — and the honest ceiling where the job belongs elsewhere.