Works with any backend npm install Zero dependencies

React Integration

Example code showing how to integrate MultipleUpload with a React component using useEffect and useRef.

Drag & drop files here
// React Component: FileUploader.jsx
import React, { useEffect, useRef } from 'react';
import MultipleUpload from 'multipleupload';

function FileUploader({ uploadUrl, onComplete }) {
 const containerRef = useRef(null);
 const uploaderRef = useRef(null);

 useEffect(() => {
 uploaderRef.current = new MultipleUpload(containerRef.current, {
 uploadUrl: uploadUrl,
 onTaskComplete: (task) => {
 if (onComplete) onComplete(task);
 }
 });

 return () => {
 // Cleanup on unmount
 if (uploaderRef.current && uploaderRef.current.destroy) {
 uploaderRef.current.destroy();
 }
 };
 }, [uploadUrl]);

 return (
 <div ref={containerRef} className="mu-uploader">
 <button type="button" className="mu-select-btn">Select Files</button>
 <input type="file" className="mu-file-input" multiple style={{ display: 'none' }} />
 <div className="mu-dropzone">
 <div className="mu-dropzone-text">Drag &amp; drop files here</div>
 </div>
 <div className="mu-queue"></div>
 </div>
 );
}

export default FileUploader;
// Usage in your App
import FileUploader from './FileUploader';

function App() {
 return (
 <FileUploader
 uploadUrl="/api/upload"
 onComplete={(task) => console.log('Uploaded:', task.name)}
 />
 );
}

Why a ref and not state

The uploader owns a piece of DOM and manages it imperatively, which is the one thing React does not want to re-render underneath. Holding the container in a ref and the instance in another keeps it outside the render cycle entirely, so React never replaces nodes the component is still using.

The cleanup that matters

Returning destroy() from useEffect is not optional. Without it, navigating away mid-upload leaves listeners bound to detached nodes and a transfer running with nowhere to report to. In development, React 18's StrictMode mounts effects twice on purpose — so a missing cleanup shows up immediately as two uploaders in one container, which is a useful early warning rather than a nuisance.

Keep the dependency array honest. Listing a callback that is redefined on every render tears the uploader down and rebuilds it on every render too; wrap it in useCallback or keep it in a ref.