react-headless-uploader
v1.0.1
Published
A lightweight, headless, and highly customizable React image uploader library
Maintainers
Readme
React Headless Uploader
A lightweight, headless, and highly customizable React image uploader library with drag-and-drop support, file validation, image compression, and progress tracking.
Features
- Drag and drop upload
- Manual file selection
- Multiple file support
- File type validation (images: jpg, png, gif, webp, etc.)
- Max file size validation
- Custom validation function
- Per-file upload progress
- Cancel and retry uploads
- Optional image compression before upload
- Fully headless - full control over UI
Installation
npm install react-headless-uploaderor
yarn add react-headless-uploaderQuick Start
import { useUploader } from 'react-headless-uploader';
function App() {
const uploader = useUploader({
accept: ['image/jpeg', 'image/png'],
maxSize: 2 * 1024 * 1024,
multiple: true,
onUpload: async (file, onProgress) => {
// Your upload logic here
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const progress = (event.loaded / event.total) * 100;
onProgress(progress);
}
};
return new Promise((resolve, reject) => {
xhr.onload = () => resolve(xhr.response);
xhr.onerror = () => reject(xhr.statusText);
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}
});
return (
<div {...uploader.getRootProps()}>
<input {...uploader.getInputProps()} />
<p>Drag & drop or click to upload</p>
</div>
);
}API
useUploader
const uploader = useUploader(options);Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| accept | string[] | [] | Array of accepted MIME types |
| maxSize | number | Number.MAX_SAFE_INTEGER | Maximum file size in bytes |
| multiple | boolean | true | Allow multiple file selection |
| compress | boolean | false | Compress images before upload |
| compressionOptions | CompressionOptions | See below | Options for image compression |
| onUpload | (file, onProgress) => Promise | Required | Upload callback function |
| validate | (file) => boolean \| string | undefined | Custom validation function |
CompressionOptions
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| maxSizeMB | number | 1 | Maximum file size in MB |
| maxWidthOrHeight | number | 1024 | Maximum width or height in pixels |
| useWebWorker | boolean | true | Use web worker for compression |
| maxIteration | number | 10 | Maximum compression iterations |
| exifRotation | number | 1 | EXIF rotation option |
Return Value
| Property | Type | Description |
|----------|------|-------------|
| getRootProps | (props?) => object | Props for dropzone container |
| getInputProps | (props?) => object | Props for file input element |
| files | FileItem[] | Array of files with status and progress |
| upload | () => Promise<void> | Start uploading all pending files |
| cancel | (fileId) => void | Cancel a specific file upload |
| retry | (fileId) => void | Retry a failed file upload |
| remove | (fileId) => void | Remove a file from the list |
| clearAll | () => void | Clear all files |
| isDragActive | boolean | Whether user is dragging files |
FileItem
interface FileItem {
id: string;
file: File;
name: string;
size: number;
type: string;
progress: number;
status: 'idle' | 'uploading' | 'success' | 'error';
error?: string;
preview?: string;
compressedFile?: File;
}Complete Example
import React from 'react';
import { useUploader, FilePreview, ProgressBar } from 'react-headless-uploader';
function App() {
const uploader = useUploader({
accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
maxSize: 5 * 1024 * 1024, // 5MB
multiple: true,
compress: true,
compressionOptions: {
maxSizeMB: 1,
maxWidthOrHeight: 1024,
},
onUpload: async (file, onProgress) => {
// Simulated upload
for (let i = 0; i <= 100; i += 10) {
await new Promise(r => setTimeout(r, 100));
onProgress(i);
}
return { success: true };
}
});
return (
<div style={{ maxWidth: '500px', margin: '0 auto', padding: '20px' }}>
<div
{...uploader.getRootProps()}
style={{
border: uploader.isDragActive ? '2px dashed #3b82f6' : '2px dashed #d1d5db',
borderRadius: '8px',
padding: '40px',
textAlign: 'center',
cursor: 'pointer',
backgroundColor: uploader.isDragActive ? '#eff6ff' : '#f9fafb',
transition: 'all 0.2s',
}}
>
<input {...uploader.getInputProps()} />
<p style={{ color: '#6b7280', margin: 0 }}>
{uploader.isDragActive
? 'Drop the files here...'
: 'Drag & drop or click to select'}
</p>
</div>
{uploader.files.length > 0 && (
<div style={{ marginTop: '20px' }}>
{uploader.files.map((file) => (
<FilePreview
key={file.id}
file={file}
onRemove={() => uploader.remove(file.id)}
style={{ marginBottom: '8px' }}
/>
))}
</div>
)}
{uploader.files.length > 0 && (
<div style={{ marginTop: '20px', display: 'flex', gap: '10px' }}>
<button
onClick={uploader.upload}
disabled={uploader.files.every(f => f.status === 'success')}
style={{
padding: '10px 20px',
backgroundColor: '#3b82f6',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
}}
>
Upload All
</button>
<button
onClick={uploader.clearAll}
style={{
padding: '10px 20px',
backgroundColor: '#ef4444',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
}}
>
Clear All
</button>
</div>
)}
</div>
);
}Components
Dropzone
A simple wrapper component for the dropzone area. Uses the same props as a regular div.
import { Dropzone } from 'react-headless-uploader';
<Dropzone className="my-dropzone" style={{ padding: '20px' }}>
{/* Content */}
</Dropzone>ProgressBar
A minimal progress bar component.
import { ProgressBar } from 'react-headless-uploader';
<ProgressBar progress={75} />FilePreview
A file preview component showing thumbnail, name, size, and status.
import { FilePreview } from 'react-headless-uploader';
{uploader.files.map(file => (
<FilePreview
key={file.id}
file={file}
onRemove={() => uploader.remove(file.id)}
/>
))}Utilities
formatFileSize
Format bytes to human-readable size string.
import { formatFileSize } from 'react-headless-uploader';
formatFileSize(1024); // "1 KB"
formatFileSize(1048576); // "1 MB"truncateFileName
Truncate long file names.
import { truncateFileName } from 'react-headless-uploader';
truncateFileName('very-long-filename.jpg', 20); // "very-long-f...name.jpg"License
MIT
Support the Project
If you find this package useful, please consider supporting its development!
GitHub Sponsors
Buy Me a Coffee
Bitcoin Donation
BTC Address: bc1qf2m6sdhtnhw4apqj29hjj26m7enamzknzd3secBitcoin QR Code:
bitcoin:bc1qf2m6sdhtnhw4apqj29hjj26m7enamzknzd3sec?label=react-headless-uploaderCredit
Created by Hardik Gajera
- GitHub: @Hardik8140
- npm: react-headless-uploader
If you use this package, please star the repository and consider contributing!
