Implementing drag and drop file upload in React

Drag and drop file upload functionality enhances user experience by providing an intuitive way to handle files in web applications. In this post, we use the well-supported library react-dropzone to build a robust, type-safe file upload component in React.
Setting up the project
Begin by installing the required dependency using either npm or yarn:
npm install react-dropzone
# Or
yarn add react-dropzone
Creating a TypeScript file upload component
Below is a complete example of a drag and drop file upload component implemented with React and TypeScript. This component leverages react-dropzone to manage file selection, validation, and error handling.
import React, { useCallback, useState } from 'react'
import { useDropzone, FileRejection } from 'react-dropzone'
interface UploadProps {
onUploadComplete?: (files: File[]) => void
maxFiles?: number
maxSize?: number
}
const DragAndDropUpload: React.FC<UploadProps> = ({
onUploadComplete,
maxFiles = 1,
maxSize = 5242880, // 5MB
}) => {
const [files, setFiles] = useState<File[]>([])
const [uploadError, setUploadError] = useState<string | null>(null)
const onDropAccepted = useCallback((acceptedFiles: File[]) => {
setFiles(acceptedFiles)
setUploadError(null)
if (onUploadComplete) {
onUploadComplete(acceptedFiles)
}
}, [onUploadComplete])
const onDropRejected = useCallback((fileRejections: FileRejection[]) => {
const errors = fileRejections.map(
(rejection) => `${rejection.file.name}: ${rejection.errors[0].message}`
)
setUploadError(errors.join('\n'))
}, [])
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDropAccepted,
onDropRejected,
maxFiles,
maxSize,
accept: {
'image/*': ['.jpeg', '.jpg', '.png', '.gif'],
'application/pdf': ['.pdf'],
},
})
return (
<div className="upload-container">
<div
{...getRootProps()}
className={`dropzone ${isDragActive ? 'active' : ''}`}
>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop the files here</p>
) : (
<p>Drag and drop files here, or click to select files</p>
)}
</div>
{uploadError && <div className="error-message">{uploadError}</div>}
{files.length > 0 && (
<div className="file-list">
<h4>Selected Files:</h4>
<ul>
{files.map((file) => (
<li key={file.name}>
{file.name} - {(file.size / 1024).toFixed(2)} KB
</li>
))}
</ul>
</div>
)}
</div>
)
}
export default DragAndDropUpload
Handling file uploads
This function demonstrates how to handle file uploads using the Fetch API along with proper error handling. It converts the selected files into a FormData object and sends them to your back-end API endpoint.
const uploadFiles = async (files: File[]): Promise<void> => {
const formData = new FormData()
files.forEach((file) => {
formData.append('files', file)
})
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
},
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.message || 'Upload failed')
}
const result = await response.json()
console.log('Upload successful:', result)
} catch (error: any) {
console.error('Upload error:', error)
throw error
}
}
Styling the component
The following CSS styles help create a clean and responsive design for the upload component. Adjust these styles as needed for your application.
.upload-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.dropzone {
border: 2px dashed #0087f7;
border-radius: 4px;
padding: 20px;
text-align: center;
background: #f8f9fa;
cursor: pointer;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #00e676;
background: #e3f2fd;
}
.error-message {
color: #d32f2f;
margin-top: 10px;
padding: 10px;
background: #ffebee;
border-radius: 4px;
}
.file-list {
margin-top: 20px;
}
.file-list ul {
list-style: none;
padding: 0;
}
.file-list li {
padding: 8px;
background: #f5f5f5;
margin-bottom: 8px;
border-radius: 4px;
}
Advanced file uploads with Uppy
For projects that require advanced features such as upload progress tracking, chunked uploads, or resumable uploads, Uppy is an excellent alternative. The snippet below demonstrates how to integrate Uppy (version 4.x) with React.
import Uppy from '@uppy/core'
import { Dashboard } from '@uppy/react'
import '@uppy/core/dist/style.min.css'
import '@uppy/dashboard/dist/style.min.css'
const uppy = new Uppy()
function UppyUploader() {
return <Dashboard uppy={uppy} />
}
export default UppyUploader
Uppy builds on modern web standards and works seamlessly with React, making it a powerful choice for handling complex file uploads.
Accessibility and mobile considerations
When implementing drag and drop file uploads, consider the following to ensure a great user experience across all devices:
- Ensure the dropzone is keyboard accessible by adding focus styles or attributes if needed.
- Use clear and concise instructions so users understand how to interact with the component.
- Test the dropzone on mobile devices to verify that fallback behaviors (such as file selection via taps) work reliably.
- Consider adding ARIA attributes, such as
aria-label
, to improve accessibility for screen readers.
Usage example
Integrate the DragAndDropUpload component into your application as shown below. In this example, the
parent component handles file uploads by invoking the uploadFiles
function upon completion of the
file selection.
import React from 'react'
import DragAndDropUpload from './DragAndDropUpload'
const App: React.FC = () => {
const handleUploadComplete = async (files: File[]) => {
try {
await uploadFiles(files)
// Additional success handling can go here
} catch (error) {
console.error('Upload failed:', error)
}
}
return (
<div>
<h1>File Upload</h1>
<DragAndDropUpload
onUploadComplete={handleUploadComplete}
maxFiles={5}
maxSize={5242880}
/>
</div>
)
}
export default App
Conclusion
In this post, we built a robust, type-safe drag and drop file upload component using react-dropzone. We covered setting up the project, implementing file validation and error handling, and styling the component. Additionally, we explored how Uppy can be integrated for advanced upload features and offered tips for ensuring accessibility and mobile friendliness.
For more advanced features like upload progress tracking, chunked uploads, and resumable uploads, consider using Uppy—a powerful, open source file uploader developed by Transloadit (https://uppy.io).