As a developer, I was working on a React project where I needed to allow users to upload images and documents to the application. As simple as this sounds, implementing file uploads in React can be tricky if you don’t understand the fundamentals.
In this article, I’ll walk you through multiple approaches to implementing file uploads in React JS, from basic implementations to more advanced solutions using popular libraries.
So let’s get in!
Upload Files in React JS
Now, I will explain the methods to upload files in React JS.
Method 1: Basic File Upload Using React State
Let’s start with the simplest approach, using React’s state to handle a single file upload.
import React, { useState } from 'react';
function BasicFileUpload() {
const [selectedFile, setSelectedFile] = useState(null);
const [isFilePicked, setIsFilePicked] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const changeHandler = (event) => {
setSelectedFile(event.target.files[0]);
setIsFilePicked(true);
};
const handleSubmission = async () => {
if (!selectedFile) return;
setIsUploading(true);
const formData = new FormData();
formData.append('file', selectedFile);
try {
const response = await fetch('https://your-api-endpoint.com/upload', {
method: 'POST',
body: formData,
});
const result = await response.json();
console.log('Success:', result);
// Handle success - maybe show a success message
} catch (error) {
console.error('Error:', error);
// Handle error - show error message
} finally {
setIsUploading(false);
}
};
return (
<div>
<h2>Upload a File</h2>
<input type="file" name="file" onChange={changeHandler} />
{isFilePicked ? (
<div>
<p>Filename: {selectedFile.name}</p>
<p>Filetype: {selectedFile.type}</p>
<p>Size in bytes: {selectedFile.size}</p>
</div>
) : (
<p>Select a file to show details</p>
)}
<div>
<button onClick={handleSubmission} disabled={!isFilePicked || isUploading}>
{isUploading ? 'Uploading...' : 'Submit'}
</button>
</div>
</div>
);
}
export default BasicFileUpload;I executed the above example code and added the screenshot below.

This basic implementation allows users to:
- Select a file using the file input
- See details about the selected file
- Upload the file to a server endpoint
Method 2: Multiple File Uploads in React
Often, you’ll need to allow users to upload multiple files at once. Here’s how to modify our example to handle multiple file uploads:
import React, { useState } from 'react';
function MultipleFileUpload() {
const [selectedFiles, setSelectedFiles] = useState([]);
const [isUploading, setIsUploading] = useState(false);
const changeHandler = (event) => {
setSelectedFiles(Array.from(event.target.files));
};
const handleSubmission = async () => {
if (selectedFiles.length === 0) return;
setIsUploading(true);
const formData = new FormData();
selectedFiles.forEach((file, index) => {
formData.append(`file${index}`, file);
});
try {
const response = await fetch('https://your-api-endpoint.com/upload-multiple', {
method: 'POST',
body: formData,
});
const result = await response.json();
console.log('Success:', result);
// Handle success
} catch (error) {
console.error('Error:', error);
// Handle error
} finally {
setIsUploading(false);
}
};
return (
<div>
<h2>Upload Multiple Files</h2>
<input type="file" name="files" onChange={changeHandler} multiple />
{selectedFiles.length > 0 ? (
<div>
<p>Files selected: {selectedFiles.length}</p>
<ul>
{selectedFiles.map((file, index) => (
<li key={index}>
{file.name} - {file.size} bytes
</li>
))}
</ul>
</div>
) : (
<p>Select files to upload</p>
)}
<div>
<button
onClick={handleSubmission}
disabled={selectedFiles.length === 0 || isUploading}
>
{isUploading ? 'Uploading...' : 'Submit'}
</button>
</div>
</div>
);
}
export default MultipleFileUpload;I executed the above example code and added the screenshot below.

The key differences here are:
- Adding the
multipleattribute to the file input - Handling an array of files instead of a single file
- Appending multiple files to the FormData object
Method 3: Drag and Drop File Upload
A more user-friendly approach is to implement drag-and-drop functionality. Here’s how to create a drag-and-drop file upload component:
import React, { useState, useRef } from 'react';
import './DragDropUpload.css'; // You'll need to create this CSS file
function DragDropUpload() {
const [files, setFiles] = useState([]);
const [isDragging, setIsDragging] = useState(false);
const fileInputRef = useRef(null);
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const droppedFiles = Array.from(e.dataTransfer.files);
setFiles(prevFiles => [...prevFiles, ...droppedFiles]);
};
const handleFileSelect = (e) => {
const selectedFiles = Array.from(e.target.files);
setFiles(prevFiles => [...prevFiles, ...selectedFiles]);
};
const removeFile = (index) => {
setFiles(files.filter((_, i) => i !== index));
};
const uploadFiles = async () => {
if (files.length === 0) return;
const formData = new FormData();
files.forEach((file, index) => {
formData.append(`file${index}`, file);
});
try {
const response = await fetch('https://your-api-endpoint.com/upload-multiple', {
method: 'POST',
body: formData,
});
const result = await response.json();
console.log('Success:', result);
setFiles([]);
// Show success message
} catch (error) {
console.error('Error:', error);
// Show error message
}
};
return (
<div className="upload-container">
<div
className={`drop-zone ${isDragging ? 'active' : ''}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current.click()}
>
<p>Drag and drop files here or click to select files</p>
<input
type="file"
multiple
onChange={handleFileSelect}
ref={fileInputRef}
style={{ display: 'none' }}
/>
</div>
{files.length > 0 && (
<div className="file-list">
<h3>Selected Files:</h3>
<ul>
{files.map((file, index) => (
<li key={index}>
{file.name}
<button onClick={() => removeFile(index)}>Remove</button>
</li>
))}
</ul>
<button onClick={uploadFiles}>Upload All Files</button>
</div>
)}
</div>
);
}
export default DragDropUpload;For this to work properly, you’ll need some CSS. Here’s a simple example for DragDropUpload.css:
.upload-container {
max-width: 800px;
margin: 0 auto;
}
.drop-zone {
border: 2px dashed #ccc;
border-radius: 4px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: border 0.3s ease;
}
.drop-zone.active {
border-color: #007bff;
background-color: #f8f9fa;
}
.file-list {
margin-top: 20px;
}
.file-list ul {
list-style: none;
padding: 0;
}
.file-list li {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
button {
padding: 8px 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0069d9;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}I executed the above example code and added the screenshot below.

Method 4: Use React Dropzone Library
Instead of building drag-and-drop functionality from scratch, you can use the popular React-Dropzone library:
import React, { useState, useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
function ReactDropzoneUpload() {
const [files, setFiles] = useState([]);
const [uploading, setUploading] = useState(false);
const onDrop = useCallback(acceptedFiles => {
setFiles(prevFiles => [...prevFiles, ...acceptedFiles]);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'image/*': ['.jpeg', '.jpg', '.png'],
'application/pdf': ['.pdf']
}
});
const removeFile = (index) => {
setFiles(files.filter((_, i) => i !== index));
};
const uploadFiles = async () => {
if (files.length === 0) return;
setUploading(true);
const formData = new FormData();
files.forEach((file, index) => {
formData.append(`file${index}`, file);
});
try {
const response = await fetch('https://your-api-endpoint.com/upload', {
method: 'POST',
body: formData,
});
const result = await response.json();
console.log('Success:', result);
setFiles([]);
/ Show success message
} catch (error) {
console.error('Error:', error);
// Show error message
} finally {
setUploading(false);
}
};
return (
<div className="upload-container">
<div
{...getRootProps()}
className={`dropzone ${isDragActive ? 'active' : ''}`}
>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop the files here...</p>
) : (
<p>Drag & drop some files here, or click to select files</p>
)}
</div>
{files.length > 0 && (
<div className="file-list">
<h3>Selected Files:</h3>
<ul>
{files.map((file, index) => (
<li key={index}>
{file.name} - {(file.size / 1024).toFixed(2)} KB
<button onClick={() => removeFile(index)}>Remove</button>
</li>
))}
</ul>
<button onClick={uploadFiles} disabled={uploading}>
{uploading ? 'Uploading...' : 'Upload All Files'}
</button>
</div>
)}
</div>
);
}
export default ReactDropzoneUpload;The react-dropzone library makes it much easier to implement drag-and-drop file uploads with features like:
- File type validation
- Size limits
- Multiple file handling
- Preview generation
Method 5: Upload Files with Progress Tracking
When uploading large files, it’s helpful to show users a progress indicator. We can use the XMLHttpRequest object to track upload progress:
import React, { useState } from 'react';
function ProgressFileUpload() {
const [selectedFile, setSelectedFile] = useState(null);
const [uploadProgress, setUploadProgress] = useState(0);
const [uploading, setUploading] = useState(false);
const handleFileChange = (event) => {
setSelectedFile(event.target.files[0]);
setUploadProgress(0);
};
const uploadFile = () => {
if (!selectedFile) return;
setUploading(true);
const formData = new FormData();
formData.append('file', selectedFile);
// Use XMLHttpRequest for progress tracking
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
setUploadProgress(percentComplete);
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
console.log('Upload complete');
// Show success message
} else {
console.error('Upload failed');
// Show error message
}
setUploading(false);
});
xhr.addEventListener('error', () => {
console.error('Upload failed');
// Show error message
setUploading(false);
});
xhr.open('POST', 'https://your-api-endpoint.com/upload', true);
xhr.send(formData);
};
return (
<div className="upload-container">
<h2>Upload with Progress</h2>
<input type="file" onChange={handleFileChange} />
{selectedFile && (
<div className="file-details">
<p>File: {selectedFile.name}</p>
<p>Size: {(selectedFile.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
)}
{uploading && (
<div className="progress-container">
<div
className="progress-bar"
style={{ width: `${uploadProgress}%` }}
>
{uploadProgress}%
</div>
</div>
)}
<button
onClick={uploadFile}
disabled={!selectedFile || uploading}
>
{uploading ? 'Uploading...' : 'Upload File'}
</button>
</div>
);
}
export default ProgressFileUpload;
You’ll need some CSS for the progress bar:
.progress-container {
width: 100%;
height: 20px;
background-color: #e0e0e0;
border-radius: 4px;
margin: 10px 0;
}
.progress-bar {
height: 100%;
background-color: #4caf50;
border-radius: 4px;
text-align: center;
color: white;
transition: width 0.3s ease;
}
Method 6: Use Axios for File Uploads
Axios provides a cleaner API for handling HTTP requests, including file uploads with progress tracking:
import React, { useState } from 'react';
import axios from 'axios';
function AxiosFileUpload() {
const [selectedFile, setSelectedFile] = useState(null);
const [uploadProgress, setUploadProgress] = useState(0);
const [uploading, setUploading] = useState(false);
const handleFileChange = (event) => {
setSelectedFile(event.target.files[0]);
setUploadProgress(0);
};
const uploadFile = async () => {
if (!selectedFile) return;
setUploading(true);
const formData = new FormData();
formData.append('file', selectedFile);
try {
await axios.post('https://your-api-endpoint.com/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setUploadProgress(percentCompleted);
}
});
console.log('Upload successful');
// Show success message
} catch (error) {
console.error('Error uploading file:', error);
// Show error message
} finally {
setUploading(false);
}
};
return (
<div className="upload-container">
<h2>Upload with Axios</h2>
<input type="file" onChange={handleFileChange} />
{selectedFile && (
<div className="file-details">
<p>File: {selectedFile.name}</p>
<p>Size: {(selectedFile.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
)}
{uploading && (
<div className="progress-container">
<div
className="progress-bar"
style={{ width: `${uploadProgress}%` }}
>
{uploadProgress}%
</div>
</div>
)}
<button
onClick={uploadFile}
disabled={!selectedFile || uploading}
>
{uploading ? 'Uploading...' : 'Upload File'}
</button>
</div>
);
}
export default AxiosFileUpload;
Method 7: Create a Reusable File Upload Hook
To make our code more reusable, let’s create a custom React hook for file uploads:
// useFileUpload.js
import { useState } from 'react';
import axios from 'axios';
export function useFileUpload() {
const [files, setFiles] = useState([]);
const [uploadProgress, setUploadProgress] = useState(0);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(false);
const handleFileChange = (event) => {
const newFiles = Array.from(event.target.files);
setFiles(newFiles);
setUploadProgress(0);
setError(null);
setSuccess(false);
};
const uploadFiles = async (url) => {
if (files.length === 0) return;
setUploading(true);
setError(null);
setSuccess(false);
const formData = new FormData();
files.forEach((file, index) => {
formData.append(`file${index}`, file);
});
try {
const response = await axios.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setUploadProgress(percentCompleted);
}
});
setSuccess(true);
return response.data;
} catch (err) {
setError(err.message || 'An error occurred during upload');
throw err;
} finally {
setUploading(false);
}
};
const clearFiles = () => {
setFiles([]);
setUploadProgress(0);
setError(null);
setSuccess(false);
};
return {
files,
uploadProgress,
uploading,
error,
success,
handleFileChange,
uploadFiles,
clearFiles
};
}Now we can use this hook in any component:
import React from 'react';
import { useFileUpload } from './useFileUpload';
function FileUploadComponent() {
const {
files,
uploadProgress,
uploading,
error,
success,
handleFileChange,
uploadFiles,
clearFiles
} = useFileUpload();
const handleUpload = async () => {
try {
await uploadFiles('https://your-api-endpoint.com/upload');
// Handle successful upload
} catch (err) {
// Error is already handled in the hook
console.log('Upload failed');
}
};
return (
<div className="upload-container">
<h2>File Upload</h2>
<input
type="file"
multiple
onChange={handleFileChange}
disabled={uploading}
/>
{files.length > 0 && (
<div className="file-list">
<h3>Selected Files:</h3>
<ul>
{files.map((file, index) => (
<li key={index}>
{file.name} - {(file.size / 1024).toFixed(2)} KB
</li>
))}
</ul>
</div>
)}
{uploading && (
<div className="progress-container">
<div
className="progress-bar"
style={{ width: `${uploadProgress}%` }}
>
{uploadProgress}%
</div>
</div>
)}
{error && <div className="error-message">{error}</div>}
{success && <div className="success-message">Files uploaded successfully!</div>}
<div className="button-group">
<button
onClick={handleUpload}
disabled={files.length === 0 || uploading}
Clear Files
</button>
</div>
</div>
);
}export default FileUploadComponent;
## Handling Image Preview Before Upload
When uploading images, it's often helpful to show a preview before the actual upload. Here's how to implement this:
```jsx
import React, { useState } from 'react';
function ImageUploadWithPreview() {
const [selectedImage, setSelectedImage] = useState(null);
const [preview, setPreview] = useState(null);
const handleImageChange = (event) => {
const file = event.target.files[0];
if (file) {
setSelectedImage(file);
// Create a preview URL for the image
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result);
};
reader.readAsDataURL(file);
}
};
const uploadImage = async () => {
if (!selectedImage) return;
const formData = new FormData();
formData.append('image', selectedImage);
try {
const response = await fetch('https://your-api-endpoint.com/upload-image', {
method: 'POST',
body: formData,
});
const result = await response.json();
console.log('Success:', result);
// Handle success
} catch (error) {
console.error('Error:', error);
// Handle error
}
};
return (
<div className="image-upload-container">
<h2>Upload Image with Preview</h2>
<input
type="file"
accept="image/*"
onChange={handleImageChange}
/>
{preview && (
<div className="image-preview">
<h3>Preview:</h3>
<img
src={preview}
alt="Preview"
style={{ maxWidth: '300px', maxHeight: '300px' }}
/>
</div>
)}
<button
onClick={uploadImage}
disabled={!selectedImage}
>
Upload Image
</button>
</div>
);
}
export default ImageUploadWithPreview;Read State in React JS
Build a Complete File Upload Component
Now, let’s build a more complete file upload component that combines many of the features we’ve covered:
import React, { useState, useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import axios from 'axios';
import './CompleteFileUpload.css';
function CompleteFileUpload() {
const [files, setFiles] = useState([]);
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState({});
const [successfulUploads, setSuccessfulUploads] = useState([]);
const [error, setError] = useState(null);
const onDrop = useCallback(acceptedFiles => {
// Add preview URLs for images
const newFiles = acceptedFiles.map(file => Object.assign(file, {
preview: file.type.startsWith('image/')
? URL.createObjectURL(file)
: null,
id: Math.random().toString(36).substring(2)
}));
setFiles(prevFiles => [...prevFiles, ...newFiles]);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'image/*': ['.jpeg', '.jpg', '.png', '.gif'],
'application/pdf': ['.pdf'],
'application/msword': ['.doc'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
'text/plain': ['.txt']
},
maxSize: 10485760 // 10MB
});
const removeFile = (id) => {
setFiles(files.filter(file => file.id !== id));
};
const uploadFiles = async () => {
if (files.length === 0) return;
setUploading(true);
setError(null);
// Initialize progress for each file
const progressObj = {};
files.forEach(file => {
progressObj[file.id] = 0;
});
setUploadProgress(progressObj);
const successful = [];
for (const file of files) {
const formData = new FormData();
formData.append('file', file);
try {
const response = await axios.post('https://your-api-endpoint.com/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
setUploadProgress(prev => ({
...prev,
[file.id]: percentCompleted
}));
}
});
successful.push(file.id);
} catch (err) {
setError(`Error uploading ${file.name}: ${err.message}`);
break;
}
}
setSuccessfulUploads(successful);
setUploading(false);
// Remove successfully uploaded files
if (successful.length > 0) {
setFiles(files.filter(file => !successful.includes(file.id)));
}
};
// File type icon function
const getFileIcon = (file) => {
if (file.type.startsWith('image/')) {
return '🖼️';
} else if (file.type.includes('pdf')) {
return '📄';
} else if (file.type.includes('word')) {
return '📝';
} else if (file.type.includes('text')) {
return '📃';
} else {
return '📁';
}
};
return (
<div className="complete-upload-container">
<h2>File Upload Center</h2>
<div
{...getRootProps()}
className={`complete-dropzone ${isDragActive ? 'active' : ''}`}
>
<input {...getInputProps()} />
{isDragActive ? (
<p>Drop the files here...</p>
) : (
<div>
<p>Drag & drop files here, or click to select files</p>
<p className="upload-note">
Supported formats: Images, PDF, DOC, DOCX, TXT (Max: 10MB per file)
</p>
</div>
)}
</div>
{error && <div className="upload-error">{error}</div>}
{successfulUploads.length > 0 && (
<div className="upload-success">
Successfully uploaded {successfulUploads.length} file(s)!
</div>
)}
{files.length > 0 && (
<div className="file-list-container">
<h3>Selected Files:</h3>
<ul className="file-list">
{files.map(file => (
<li key={file.id} className="file-item">
<div className="file-info">
{file.preview ? (
<img
src={file.preview}
alt="Preview"
className="file-preview"
/>
) : (
<span className="file-icon">{getFileIcon(file)}</span>
)}
<div className="file-details">
<span className="file-name">{file.name}</span>
<span className="file-size">{(file.size / 1024).toFixed(2)} KB</span>
</div>
</div>
{uploading && uploadProgress[file.id] !== undefined ? (
<div className="file-progress">
<div
className="progress-bar"
style={{ width: `${uploadProgress[file.id]}%` }}
>
{uploadProgress[file.id]}%
</div>
</div>
) : (
<button
className="remove-file"
onClick={() => removeFile(file.id)}
disabled={uploading}
>
✕
</button>
)}
</li>
))}
</ul>
<div className="upload-actions">
<button
className="upload-button"
onClick={uploadFiles}
disabled={files.length === 0 || uploading}
>
{uploading ? 'Uploading...' : `Upload ${files.length} File${files.length !== 1 ? 's' : ''}`}
</button>
</div>
</div>
)}
</div>
);
}
export default CompleteFileUpload;
Best Practices for File Uploads in React
When implementing file uploads in your React applications, keep these best practices in mind:
- Validate files on the client side – Check file types, sizes, and other constraints before uploading to save bandwidth and server resources.
- Show progress indicators – For large files, always show upload progress to provide feedback to users.
- Handle errors gracefully – Display meaningful error messages when uploads fail.
- Limit file sizes – Set reasonable file size limits to prevent server overload.
- Provide visual feedback – Show previews for images and status indicators for all uploads.
- Implement chunked uploads for very large files** – For files over 100MB, consider implementing chunked uploads to improve reliability.
- Add retry functionality – Allow users to retry failed uploads.
- Security considerations – Always validate files on the server side as well, as client-side validation can be bypassed.
Read How to Reset Form in React JS
Conclusion
In this tutorial, I’ve covered multiple approaches to implementing file uploads in React JS – from basic implementations to more advanced solutions with drag-and-drop, progress tracking, and preview functionality.
Remember that file upload functionality often requires both client-side and server-side implementation. The examples I’ve shown focus on the React (client-side) part of the equation.
Related tutorials:
- Set Up React.js Environment and Create Your First React App
- Form Validation in React.js
- React Cancel Button: 5 Methods with Examples
- Pass a Component as a Prop in React

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.