A modern, flexible file upload control for React applications.
React Upload Control is a free, lightweight and open-source file uploader library designed for modern React applications. This library is crafted to provide a high-quality developer experience (DX), making it easy to use for simple file uploads while offering extensive customization options for advanced use cases.
Check out our interactive demo cases here:
React Upload Control Demo
Born from real-world needs in a production environment, React Upload Control addresses common limitations found in existing file upload solutions. While there are many file upload libraries available, we found that most of them fell short in crucial areas:
- 🔄 No Drag-to-Reorder: Most solutions lack built-in file ordering capabilities, a crucial feature for many applications
- 📚 Documentation Gaps: Many libraries either have excessive boilerplate, insufficient documentation or lacking DX
- 🔧 Maintenance Issues: Several options are outdated or no longer actively maintained
- 🎨 Poor UI/UX: Many unstyled options result in subpar user interfaces
- 🔒 Vendor Lock-in: Some solutions tie you to specific cloud services or platforms
- 📦 Bloated Dependencies: Often file uploaders come bundled within larger UI libraries, increasing your bundle size
React Upload Control was created to solve these problems, offering a standalone, modern file uploader that's both powerful and flexible. Whether you need a simple file upload control or a feature-rich solution with file processing capabilities, you can customize it to your specific needs without compromising on quality or developer experience.
- 🚀 Modern Stack: Built with React 18+ and TypeScript for type-safe development
- 📁 Drag & Drop: Intuitive file uploading with visual feedback and validation
- 📋 File Management: Drag-to-reorder capability for organizing user uploads
- 🔄 File Processing: Optional pre/post-processing capabilities with @osmandvc/react-upload-control-processors or custom integrations
- 📷 Camera Integration: Camera integration for capturing photos directly
- 💻 Developer Experience: Simple API with comprehensive TypeScript support and documentation
- 🌐 Internationalization: Built-in i18n support for multiple languages (currently English and German)
- 🎨 Beautiful UI: Modern, responsive design powered by Tailwind CSS
- 📱 Mobile Ready: Optimized experience across all device sizes
- ⚡ Async Processing: Handle file uploads asynchronously with progress updates
- 🔍 File Preview: Built-in preview support for images
- ⚙️ Unopinionated: You decide how and where files are uploaded, no vendor lock-in
- 🔓 Open Source: Free to use and modify under the MIT license
To install React Upload Control, use npm or yarn:
npm install @osmandvc/react-upload-control
or
yarn add @osmandvc/react-upload-control
This section will guide you through the process of setting up everything you need to get started. The library is built around the concept of providers and the React Context API, which means you need to wrap your application in a UploadedFilesProvider
provider. If you want to use the default use case of a list of files and a drop area, you can do this by wrapping your FileUploadControl
component in the provider. An minimal example of this can be seen below:
import React, { PropsWithChildren } from "react";
import {
FileUploadControl,
UploadedFilesProvider,
UploadedFile,
UploadFileResult,
} from "react-upload-control";
function MyFileUploadParent(props: PropsWithChildren) {
// Your custom delete and upload handlers (more about them later)...
// function handleDelete(files: UploadedFile[]) {...}
// function handleUpload(files: UploadedFile[], onProgressChange: (...) => void) {...}
function handleFinish(files: UploadedFile[]) {
console.log(files);
}
return (
<UploadedFilesProvider
handlers={{ onUpload: handleUpload, onFinish: handleFinish }}
config={{
mimeTypes: ["image/png", "image/jpeg"],
disableSorting: false, // Disable drag-to-reorder functionality
}}
>
<FileUploadControl />
</UploadedFilesProvider>
);
}
The upload handler is a crucial part of the library that gives you complete control over how files are processed and uploaded.
The upload handler receives two parameters:
-
files
: An array ofUploadedFile
objects to be uploaded -
onProgressChange
: A callback function to update the upload progress
Here are two examples of implementing an upload handler:
async function handleUpload(
files: UploadedFile[],
onProgressChange: (
fileId: string,
progress: number,
error?: { text: string; code: string }
) => void
): Promise<UploadFileResult[]> {
try {
// Set initial progress for all files
files.forEach((file) => {
onProgressChange(file.id, 0);
});
// Create form data with all files
const formData = new FormData();
files.forEach((file, index) => {
if (file.file) {
formData.append(`files`, file.file);
}
});
// Make API call with all files
const response = await fetch("https://api.example.com/upload", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error("Upload failed");
}
// Example API response with processed file data
const result = await response.json();
// { success: true, files: [{ id: "...", url: "..." }] }
// Set progress to 100% for all files after successful upload
files.forEach((file) => {
onProgressChange(file.id, 100);
});
// Return success results with metadata from API
return files.map((file, index) => ({
fileId: file.id,
success: true,
metadata: {
url: result.files[index].url,
uploadedAt: new Date().toISOString(),
},
}));
} catch (error) {
// Set error state for all files
const errorResult = {
text: error.message || "Upload failed",
code: "BATCH_UPLOAD_ERROR",
};
// Return error results for all files
return files.map((file) => ({
fileId: file.id,
success: false,
error: errorResult,
}));
}
}
// Usage with the UploadedFilesProvider
function MyFileUpload() {
return (
<UploadedFilesProvider
handlers={{
onUpload: handleUpload,
onFinish: (files) => {
// All files are uploaded successfully at this point
const urls = files.map((f) => f.metadata.url);
console.log("Uploaded file URLs:", urls);
},
}}
config={{
mimeTypes: ["image/jpeg", "image/png", "application/pdf"],
maxFileSizeMb: 10,
}}
>
<FileUploadControl />
</UploadedFilesProvider>
);
}
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
async function handleS3Upload(
files: UploadedFile[],
onProgressChange: (
fileId: string,
progress: number,
error?: { text: string; code: string }
) => void
): Promise<UploadFileResult[]> {
// Initialize the S3 client
const s3Client = new S3Client({
region: "your-region",
credentials: {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
},
});
return Promise.all(
files.map(async (file) => {
try {
if (!file.file) throw new Error("No file provided");
// Generate a unique key for the file
const key = `uploads/${Date.now()}-${file.name}`;
// Create the upload
const upload = new Upload({
client: s3Client,
params: {
Bucket: "your-bucket-name",
Key: key,
Body: file.file,
ContentType: file.file.type,
// Optional: Set additional parameters
// ACL: 'public-read',
// Metadata: { /* your metadata */ }
},
});
// Handle upload progress
upload.on("httpUploadProgress", (progress) => {
const percentage = Math.round(
((progress.loaded || 0) * 100) / (progress.total || 100)
);
onProgressChange(file.id, percentage);
});
// Perform the upload
await upload.done();
// Return success result with the S3 URL
return {
fileId: file.id,
success: true,
metadata: {
url: `https://your-bucket-name.s3.your-region.amazonaws.com/${key}`,
},
};
} catch (error) {
console.error("S3 upload error:", error);
return {
fileId: file.id,
success: false,
error: {
text: error.message || "S3 upload failed",
code: "S3_UPLOAD_ERROR",
},
};
}
})
);
}
// Usage with the UploadedFilesProvider
function MyFileUpload() {
return (
<UploadedFilesProvider
handlers={{
onUpload: handleS3Upload,
onFinish: (files) => {
// All files are successfully uploaded at this point
const urls = files.map((f) => f.metadata.url);
console.log("Uploaded file URLs:", urls);
},
}}
config={{
mimeTypes: ["image/jpeg", "image/png", "application/pdf"],
maxFileSizeMb: 10,
}}
>
<FileUploadControl />
</UploadedFilesProvider>
);
}
The upload handler must return an array of UploadFileResult
objects - one for each uploaded file. Here's the structure:
type UploadFileResult = {
// The ID of the file that was uploaded (must match the original file.id)
fileId: string;
// Whether the upload was successful
success: boolean;
// Optional error information if success is false
error?: {
text: string; // Human-readable error message
code: string; // Error code for programmatic handling
};
// Optional metadata to attach to the file
metadata?: {
[key: string]: any; // Any additional data you want to store with the file
};
};
Example of a successful upload result:
{
fileId: "file123",
success: true,
metadata: {
url: "https://example.com/uploads/file123.jpg",
uploadedAt: "2025-01-02T12:00:00Z",
size: 1024000
}
}
Example of a failed upload result:
{
fileId: "file456",
success: false,
error: {
text: "File size exceeds server limit",
code: "SIZE_LIMIT_EXCEEDED"
}
}
The onDelete
handler is designed to be non-blocking and does not include progress tracking. This is intentional for better UX - since it's primarily used for cleanup when resetting a control with already finished uploads. Users should be able to reset the control immediately without waiting for deletion to complete or being blocked by deletion errors.
interface Props {
onDelete?: (files: UploadedFile[]) => void | Promise<void>;
}
Note: Unlike
onUpload
, theonDelete
handler won't show progress or block the UI. This ensures users can quickly reset or clear their upload state without delays.
The default component that provides the file upload interface with a FileList and FileDropArea.
<FileUploadControl
size="auto" // 'auto' | 'sm' | 'lg'
disableCamera={false} // Disable camera integration
disableFileSystem={false} // Disable file system uploads
/>
The provider component that manages the upload state and configuration.
<UploadedFilesProvider
config={{
mimeTypes: ["image/jpeg", "image/png"], // Allowed file types
maxFileSizeMb: 10, // Maximum file size in MB
multiple: true, // Allow multiple file uploads
maxFiles: 5, // Maximum number of files
locale: "en", // Locale for i18n
resetOnFinish: false, // Reset state after finish
disableSorting: false, // Disable drag-to-reorder functionality
}}
handlers={{
onUpload: handleUpload, // Upload handler function
onDelete: handleDelete, // Delete handler function
onFinish: handleFinish, // Finish handler function
}}
/>
Prop | Type | Default | Description |
---|---|---|---|
config | { mimeTypes: string[], maxFileSizeMb: number, multiple: boolean, maxFiles: number, locale: string, resetOnFinish: boolean, disableSorting: boolean } | required | Configuration object |
handlers | { onUpload: (files: UploadedFile[]) => Promise<UploadFileResult[]>, onDelete: (files: UploadedFile[]) => Promise<UploadFileResult[]>, onFinish: (files: UploadedFile[]) => void } | required | Handlers object |
children | React.ReactNode | required | Child components to render |
initFiles | UploadedFile[] | undefined | Initial files to populate the uploader with |
locale | string | 'en' | Locale for internationalization |
Prop | Type | Default | Description |
---|---|---|---|
size | 'auto' | 'sm' | 'lg' | 'auto' | Component size variant |
disableCamera | boolean | false | Disable camera integration |
disableFileSystem | boolean | false | Disable file system uploads |
className | string | undefined | Additional CSS classes |
React Upload Control supports file processing through the optional @osmandvc/react-upload-control-processors package. This enables you to process files before or after upload, with built-in support for PDF manipulation and extensible architecture for custom processors.
npm install @osmandvc/react-upload-control-processors
import {
FileUploadControl,
UploadedFile,
UploadedFilesProvider,
UploadFileResult,
} from "@osmandvc/react-upload-control";
import { processPdfToJpeg } from "@osmandvc/react-upload-control-processors";
function FileUploadTest() {
return (
<div className="max-w-lg">
<UploadedFilesProvider
config={{
mimeTypes: ["image/png", "image/jpeg", "application/pdf"],
disableSorting: true,
}}
handlers={{
onUpload: handleUpload,
onFinish: handleFinish,
preProcessFiles: {
"application/pdf": processPdfToJpeg,
},
}}
>
<FileUploadControl />
</UploadedFilesProvider>
</div>
);
}
You can see a demo of this in action here.
The useUploadFilesProvider
hook allows you to create your own file sources (like drop areas) and file destinations (like file lists) with ease. The provider handles all the complex validation and state management for you.
The provider abstracts away:
- File validation (size, type, count)
- Progress tracking
- File state management
- Error handling
- Upload coordination
🔍 Coming Soon: Detailed documentation on creating custom upload components, handling file preprocessing, and implementing advanced validation logic.
React Upload Control is highly customizable. You can tailor it to meet your specific needs by adjusting its configuration and using its extensive API. Detailed documentation coming soon.
We welcome contributions from the community. Feel free to open issues or submit pull requests on our GitHub repository.
Please note that while React Upload Control is in an early state and has been battle-tested, bugs may still appear. We would appreciate it if you report any issues you encounter so they can be fixed as soon as possible. Contributions to improve the library are very welcome.
Because the library is in its early stages, we are open to suggestions and feedback. If you have any ideas for new features or enhancements, please don't hesitate to share them with us.
This project is licensed under the MIT License.
MIT © Osman Deveci