react ~9 KB 0 deps v0.2.1 ↗ GitHub ↗

@mshafiqyajid/react-file-upload

Headless file upload hook and styled component. Revamped dropzone UI with customisable labels, drag-and-drop, image previews, custom preview renderer, validation callbacks, multi-file. Async uploader with progress, abort, retry — concurrency-aware queue.

Playground #

Drop files here

or

Props
TSX
import { FileUploadStyled } from "@mshafiqyajid/react-file-upload/styled";
import "@mshafiqyajid/react-file-upload/styles.css";

<FileUploadStyled />

Install #

npm install @mshafiqyajid/react-file-upload

Quick start #

import { FileUploadStyled } from "@mshafiqyajid/react-file-upload/styled";
import "@mshafiqyajid/react-file-upload/styles.css";

<FileUploadStyled
  multiple
  accept="image/*"
  maxSize={5242880}
  showPreview
  onFiles={(result) => console.log(result.files, result.errors)}
/>

Async uploader #

Pass an uploader to upload accepted files automatically. Each file becomes an UploadItem with status (queued | uploading | success | error | aborted) and a progress you control by calling ctx.onProgress(fraction). The hook runs uploads concurrently up to concurrency (default 3) and respects ctx.signal for abort.

<FileUploadStyled
  multiple
  uploader={async (file, { signal, onProgress }) => {
    const xhr = new XMLHttpRequest();
    xhr.upload.onprogress = (e) => e.lengthComputable && onProgress(e.loaded / e.total);
    signal.addEventListener("abort", () => xhr.abort());

    return await new Promise((resolve, reject) => {
      xhr.onload = () => resolve(JSON.parse(xhr.responseText));
      xhr.onerror = () => reject(new Error("upload failed"));
      xhr.open("POST", "/api/upload");
      const fd = new FormData();
      fd.append("file", file);
      xhr.send(fd);
    });
  }}
  concurrency={3}
  onUpload={(item) => console.log(item.status, item.progress)}
/>

Headless consumers also get uploads, retryUpload(id), abortUpload(id), and abortAll().

Headless #

import { useFileUpload } from "@mshafiqyajid/react-file-upload";

const {
  getRootProps,
  getInputProps,
  isDragOver,
  files,
  removeFile,
  open,
} = useFileUpload({
  multiple: true,
  accept: "image/*",
});

return (
  <div {...getRootProps()} data-drag-over={isDragOver} className="dz">
    <input {...getInputProps()} />
    <button onClick={open}>Browse</button>
    <ul>
      {files.map((file, i) => (
        <li key={i}>
          {file.name}
          <button onClick={() => removeFile(i)}>×</button>
        </li>
      ))}
    </ul>
  </div>
);

API #

PropTypeDefaultDescription
variant"dropzone" | "button""dropzone"Upload widget style
multiplebooleanfalseAllow multiple files
acceptstringAccepted file types (MIME or extension)
maxSizenumberMax file size in bytes
maxFilesnumberMax number of files
showPreviewbooleantrueShow image previews
size"sm" | "md" | "lg""md"Component size
onFiles(result) => voidCalled when files change. result.accepted, result.rejected
uploader(file, ctx) => Promise<T>Async upload function. ctx: { signal, onProgress }.
autoUploadbooleantrue when uploader providedStart uploading newly accepted files automatically
concurrencynumber3Max simultaneous uploads
onUpload(item) => voidCalled whenever an upload item changes state
disabledbooleanfalseDisable interaction
uploadTextstring"Drop files here"Main dropzone label
browseTextstring"browse"Browse link text
renderPreview(file, onRemove, upload?) => ReactNodeCustom preview renderer; receives the matching UploadItem when an uploader is provided
Edit this page on GitHub