Handling file uploads in React applications requires special consideration compared to regular form data. Here’s a comprehensive guide to implementing file uploads effectively:
1. Basic File Upload
Using Native HTML Input
function BasicFileUpload() {
const handleFileChange = (e) => {
const file = e.target.files[0];
console.log('Selected file:', file);
};
return (
<div>
<input type="file" onChange={handleFileChange} />
</div>
);
}
Handling Multiple Files
function MultiFileUpload() {
const handleFilesChange = (e) => {
const files = Array.from(e.target.files);
console.log('Selected files:', files);
};
return (
<div>
<input
type="file"
multiple
onChange={handleFilesChange}
/>
</div>
);
}
2. File Upload with Form Data
Using FormData API
function FormWithFileUpload() {
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('file', e.target.file.files[0]);
formData.append('title', e.target.title.value);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('Upload success:', result);
} catch (error) {
console.error('Upload failed:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="title" placeholder="File title" />
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>
);
}
3. Previewing Files Before Upload
Image Preview
function ImageUploadWithPreview() {
const [preview, setPreview] = useState(null);
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result);
};
reader.readAsDataURL(file);
}
};
return (
<div>
<input type="file" accept="image/*" onChange={handleImageChange} />
{preview && (
<div>
<img src={preview} alt="Preview" style={{ maxWidth: '100%', maxHeight: '200px' }} />
</div>
)}
</div>
);
}
4. File Validation
Size and Type Validation
function ValidatedFileUpload() {
const [error, setError] = useState('');
const validateFile = (file) => {
const validTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!validTypes.includes(file.type)) {
setError('Only JPG, PNG, or GIF files are allowed');
return false;
}
if (file.size > 5 * 1024 * 1024) { // 5MB
setError('File size must be less than 5MB');
return false;
}
setError('');
return true;
};
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file && validateFile(file)) {
console.log('Valid file selected:', file);
}
};
return (
<div>
<input type="file" accept="image/*" onChange={handleFileChange} />
{error && <div style={{ color: 'red' }}>{error}</div>}
</div>
);
}
5. Upload Progress Tracking
Tracking Upload Progress
function UploadWithProgress() {
const [progress, setProgress] = useState(0);
const handleUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
setProgress(percentComplete);
}
});
xhr.open('POST', '/api/upload');
xhr.send(formData);
};
return (
<div>
<input type="file" onChange={handleUpload} />
{progress > 0 && (
<div>
<progress value={progress} max="100" />
<span>{progress}%</span>
</div>
)}
</div>
);
}
6. Using Libraries for Advanced Features
React Dropzone (Drag & Drop)
import { useDropzone } from 'react-dropzone';
function DropzoneUpload() {
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
accept: {
'image/*': ['.jpeg', '.png', '.gif']
},
maxFiles: 3,
maxSize: 5 * 1024 * 1024 // 5MB
});
return (
<div {...getRootProps({ className: 'dropzone' })}>
<input {...getInputProps()} />
<p>Drag & drop files here, or click to select</p>
<ul>
{acceptedFiles.map(file => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
))}
</ul>
</div>
);
}
7. Handling Large Files (Chunk Uploads)
Chunked File Upload
async function uploadFileInChunks(file, chunkSize = 5 * 1024 * 1024) {
const totalChunks = Math.ceil(file.size / chunkSize);
const fileId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
const start = chunkNumber * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('fileId', fileId);
formData.append('chunkNumber', chunkNumber);
formData.append('totalChunks', totalChunks);
formData.append('fileName', file.name);
formData.append('chunk', chunk);
await fetch('/api/upload-chunk', {
method: 'POST',
body: formData
});
}
return fileId;
}
function ChunkedFileUpload() {
const handleFileChange = async (e) => {
const file = e.target.files[0];
if (file) {
const fileId = await uploadFileInChunks(file);
console.log('Upload complete, file ID:', fileId);
}
};
return <input type="file" onChange={handleFileChange} />;
}
Best Practices
- Client-Side Validation:
- Validate file type (accept attribute)
- Check file size before upload
- Provide clear error messages
- Server-Side Validation:
- Always validate on server (client validation can be bypassed)
- Check MIME type, not just extension
- Scan for malware if applicable
- User Experience:
- Show previews for images
- Display upload progress
- Provide clear success/error feedback
- Security Considerations:
- Use HTTPS for file uploads
- Store files outside web root when possible
- Implement proper file naming to prevent directory traversal
- Performance:
- Consider chunked uploads for large files
- Compress images before upload when appropriate
- Implement client-side resizing for images
These techniques cover most file upload scenarios in React applications. Choose the approach that best fits your specific requirements and user experience goals.