Skip to content

Getting started

react-upload-kit is a headless, adapter-based file upload toolkit for React. It handles the hard parts — drag & drop, validation, concurrency, retry, cancellation, previews — while you own the UI.

Most upload libraries either ship opinionated markup that fights your design system, or leave you wiring low-level browser APIs by hand. This library sits in between: composable hooks, optional renderless components, and a single UploadAdapter you control.

Installation

bash
npm install react-upload-kit

Peer dependency: React 18 or later. There are no other runtime dependencies.

Two entrypoints

ts
// Hooks, core utilities, and types
import { useUploader, useDropzone, usePaste, useFilePreview } from 'react-upload-kit';

// Optional renderless components
import { Dropzone, FileList, UploadTrigger } from 'react-upload-kit/components';

The package is dual CJS/ESM, sideEffects: false, and tree-shakeable. Import only what you use.

Minimal example

The adapter is the only required piece of infrastructure. Everything else is optional.

tsx
import { useUploader, useDropzone } from 'react-upload-kit';

const uploadAdapter = async (file, { onProgress, signal }) => {
  const body = new FormData();
  body.append('file', file);

  const xhr = new XMLHttpRequest();

  return new Promise((resolve, reject) => {
    signal.addEventListener('abort', () => xhr.abort());

    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        onProgress(Math.round((e.loaded / e.total) * 100));
      }
    });

    xhr.addEventListener('load', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        reject(new Error(`Upload failed: ${xhr.status}`));
      }
    });

    xhr.addEventListener('error', () => reject(new Error('Network error')));
    xhr.open('POST', '/api/upload');
    xhr.send(body);
  });
};

function FileUploader() {
  const uploader = useUploader({
    adapter: uploadAdapter,
    accept: ['image/*', '.pdf'],
    maxFileSize: 10 * 1024 * 1024,
    maxFiles: 5,
    autoUpload: true,
    concurrency: 3,
    maxRetries: 2,
  });

  const dropzone = useDropzone({
    onDrop: uploader.addFiles,
    accept: ['image/*', '.pdf'],
  });

  return (
    <div>
      <div {...dropzone.getRootProps()}>
        <input {...dropzone.getInputProps()} />
        {dropzone.isDragActive
          ? 'Drop files here…'
          : 'Drag & drop files, or click to select'}
      </div>

      {uploader.files.map((file) => (
        <div key={file.id}>
          <span>{file.file.name}</span>
          <span>{file.progress}%</span>
          <span>{file.status}</span>
          {file.status === 'uploading' && (
            <button onClick={() => uploader.cancelFile(file.id)}>Cancel</button>
          )}
          {file.status === 'error' && (
            <button onClick={() => uploader.retryFile(file.id)}>Retry</button>
          )}
          <button onClick={() => uploader.removeFile(file.id)}>Remove</button>
        </div>
      ))}
    </div>
  );
}

How the pieces fit

PieceRole
AdapterYour upload function. Receives a File, reports progress, respects abort.
useUploaderOrchestrator: file state, validation, queue, retry, cancel.
useDropzoneProp-getters for a drag & drop / click-to-select surface.
usePasteClipboard images into the same addFiles pipeline.
useFilePreviewObject URL thumbnails with automatic cleanup.
ComponentsThin render-prop wrappers if you prefer a declarative API.

Keep validation rules on useUploader as the source of truth. Mirror accept (and size limits, if you want live drag feedback) on useDropzone. Files added via paste or a custom button still go through the uploader.

Next

Released under the MIT License.