Skip to content

Repository files navigation

react-file-progress

React file upload component with two progress bars — one for reading the file from the user's hard disk or SSD into the browser, and a separate one for uploading it to your server. Drag and drop, TypeScript, zero dependencies.

NPM License

▶ Try the live demo

What is a "file loading" progress bar?

When someone picks a file, the browser does not have it yet. The file is still sitting on their hard drive or SSD, and it has to be read from that disk into the browser's memory before your JavaScript can touch it. For a big video or a large PDF that read takes real time, and until now users stared at a frozen screen while it happened.

This library shows a progress bar for that read — from the hard disk / SSD into the browser. No server, no network, no upload. Just "your file is being loaded, here's how far along it is".

Progress bar showing a file being read from the hard disk into the browser, then sitting ready with its name shown

The GIF above is slowed down on purpose so you can see the bar move. On a fast local SSD, reading a 150MB file into the browser takes only about 90 milliseconds — measured, not guessed. The bar earns its keep where that read is genuinely slow: network drives and SMB shares, cloud-synced folders like iCloud or Google Drive, external USB disks, SD cards, and phones. On a quick SSD it simply flashes past, which is the correct behaviour.

Sending that file to your server afterwards is a completely separate step that you trigger yourself — with its own, second progress bar.

  • Zero dependencies, no CSS import, TypeScript types, SSR safe
  • Works with no server at allurl is optional
  • Every part swappable: your own card, your own bar, or no UI at all

Install

npm install --save react-file-progress

React 16.8 or newer is required (it is a peer dependency).

The two stages

add file ──▶ loading ──▶ ready ──▶ uploading ──▶ uploaded
             automatic    file is    only when you call
                          in browser  upload()

A file always loads into the browser, with a progress bar. It then sits at ready — name shown, bytes available on item.data — until you upload it. Set uploadFileOnLoad if you'd rather that happen automatically.

In other words: stage one is disk → browser (reading the file off the hard drive), stage two is browser → server (the actual upload). Most libraries only show you the second one.

Usage

Just load files into the browser. No server involved:

import { FileUpload } from 'react-file-progress'

<FileUpload
  accept='image/*'
  maxSize={10 * 1024 * 1024}
  onLoadComplete={(item) => console.log(item.file.name, item.data)}
/>

Add a url and each ready file gets an Upload button:

<FileUpload url='/api/upload' onUploadComplete={(item) => console.log(item.response)} />

Pressing Upload sends the already-loaded file to the server with its own progress bar

Or skip the wait and upload as soon as each file lands:

<FileUpload url='/api/upload' uploadFileOnLoad />

Props

Prop What it does Default
url Where upload() sends files. Omit it to only load into the browser
uploadFileOnLoad Upload each file as soon as it finishes loading false
accept Allowed file types, same as the input attribute: '.pdf,image/*' all
maxSize Largest allowed file, in bytes
onLoadComplete File is in the browser — item.data holds the bytes
onUploadComplete Server accepted it — item.response holds the reply
onError Loading, uploading or validation failed
method headers fieldName Request shape — auth tokens go in headers POST · — · 'file'
multiple disabled label hint Drop zone basics true · false
progressBar Props for every bar (see below)
components classNames Swap parts out, or just restyle them
children Render prop — your UI, this library's logic

Rejected files appear in the same list as errors, so there's only one thing to render.

Customizing

The barprogressBar forwards to every bar:

<FileUpload url='/api/upload' progressBar={{ width: '100%', height: 10, color: '#8b5cf6', showLabel: true }} />

value status width height color trackColor radius showLabel formatLabel indeterminate. Numbers become px. It's exported standalone too: <ProgressBar value={62} showLabel />.

Whole components — override one, keep the rest:

<FileUpload components={{ Container: MyCard, ProgressBar: MyBar }} />
Slot Gets
Container children, className, style
DropZone isDragging, disabled, label, hint, browse()
Item item, upload(), cancel(), retry(), remove(), canUpload
ProgressBar value, status, plus everything above

The same uploader rendered with a custom card and a gradient progress bar

CSS — class names are stable and unhashed (rfu-root, rfu-item, rfu-progress…), bars carry data-status, and colors are custom properties:

.rfu-root {
  --rfu-bar-color: #3b82f6;          /* uploading */
  --rfu-bar-color-loading: #a78bfa;  /* loading into the browser */
  --rfu-success: #22c55e;
  --rfu-danger: #ef4444;
  --rfu-surface: #fff;
  --rfu-text: #111;
  --rfu-radius: 12px;
}

The hook

Same logic, no UI:

const { items, addFiles, upload, cancel, retry, remove, clear, isBusy } =
  useFileUpload({ url: '/api/upload' })

Each item is { id, file, data, status, loadProgress, uploadProgress, error, response }, where status is loading | ready | uploading | uploaded | error and data is the loaded Blob. upload() sends every ready file; upload(id) sends one.

item.data is kept until you remove() the file, so previews and re-uploads never re-read from disk. Several large files therefore stay in memory — call remove() or clear() when you're done with them.

FAQ

How do I show a progress bar while a file loads into the browser in React? Render <FileUpload /> with no url. Every file added is read from disk with a live progress bar and ends up at ready, with its bytes on item.data. Nothing is sent anywhere.

How do I show upload progress without uploading immediately? That is the default here. The file loads into the browser and waits. Call upload() from your own button when you're ready, or set uploadFileOnLoad to send it automatically.

Why doesn't my upload progress bar work with fetch? fetch cannot report upload progress portably — streaming request bodies need duplex: 'half', which is Chromium-only and requires HTTP/2. This library uses XMLHttpRequest, whose upload.onprogress works everywhere.

How do I read a large file in the browser without freezing the UI? Blob.stream() reads it in chunks so the main thread stays responsive, and each chunk updates progress. This library does that for you and falls back to FileReader on older browsers.

How do I limit file type and size? accept='image/*,.pdf' and maxSize={10 * 1024 * 1024}. Rejected files appear in the same list with an error, so there's only one thing to render.

Does it work without the built-in UI? Yes — useFileUpload() gives you the same logic headless, or pass a children render prop to keep the logic and write your own markup.

Notes

Loading uses Blob.stream() where available (Chrome 76+, Firefox 102+, Safari 14.1+) and falls back to FileReader, which reports progress just as accurately. Uploads use XMLHttpRequest because fetch still can't report upload progress portably — streaming request bodies need duplex: 'half', which is Chromium-only and requires HTTP/2.

Development

npm install          # installs and builds
npm run verify       # generated-file check, lint, typecheck, tests, build
cd example && npm install && npm run dev

The example is the live demo, and npm run deploy publishes it to GitHub Pages. Running it locally is worth it: the dev server has a deliberately throttled upload endpoint built in, so the bars move at a watchable speed instead of finishing instantly.

Styles live in src/styles.css — the single source of truth. A script turns it into src/styles.ts (injected at runtime) and copies it to dist/index.css; both run automatically on build and test. Edit the .css, never the generated .ts. Tests drive a fake XMLHttpRequest, because jsdom won't fire upload.onprogress without a real network stack.

License

MIT © codeAesthetic

About

React progress bar for reading a file from the hard disk/SSD into the browser, plus a separate one for uploading it to your server when you choose. Zero dependencies, TypeScript.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages