Click files to view code →
Build an avatar/image uploader component that allows users to select and upload profile pictures. The component should preview the selected image, validate file type and size, and provide options to change or remove the avatar.
[!TIP] Goal
Use React's
useState[!IMPORTANT] Requirements
No external dependencies needed for basic functionality.
Key Topics:
Plan of Action:
AvatarUploaderimport React, { useState, useRef } from 'react';
import './AvatarUploader.css';
function AvatarUploader({
currentAvatar = null,
maxSizeMB = 5,
onUpload,
onRemove
}) {
const [preview, setPreview] = useState(currentAvatar);
const [selectedFile, setSelectedFile] = useState(null);
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const fileInputRef = useRef(null);
// File validation
const validateFile = (file) => {
// Check file type
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!validTypes.includes(file.type)) {
return 'Please select a valid image file (JPEG, PNG, GIF, or WebP)';
}
// Check file size
const maxSize = maxSizeMB * 1024 * 1024; // Convert MB to bytes
if (file.size > maxSize) {
return `File size must be less than ${maxSizeMB}MB`;
}
return null;
};
// Handle file selection
const handleFileChange = (e) => {
const file = e.target.files[0];
if (!file) return;
// Validate file
const validationError = validateFile(file);
if (validationError) {
setError(validationError);
return;
}
setError('');
setSelectedFile(file);
// Create preview
const reader = new FileReader();
reader.onloadend = () => {
// ...CSS Styling (AvatarUploader.css):
.avatar-uploader {
max-width: 300px;
margin: 2rem auto;
padding: 2rem;
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
font-family: Arial, sans-serif;
}
.avatar-container {
position: relative;
width: 150px;
height: 150px;
margin: 0 auto 1.5rem;
border-radius: 50%;
overflow: hidden;
background-color: #f5f5f5;
border: 4px solid #e0e0e0;
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.placeholder-icon {
font-size: 4rem;
opacity: 0.8;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
// ...Usage Example:
import React, { useState } from 'react';
import AvatarUploader from './AvatarUploader';
function UserProfile() {
const [avatarUrl, setAvatarUrl] = useState(null);
const handleUpload = async (file) => {
// Simulate API upload
console.log('Uploading file:', file);
// In real app, upload to server:
// const formData = new FormData();
// formData.append('avatar', file);
// const response = await fetch('/api/upload-avatar', {
// method: 'POST',
// body: formData
// });
// const data = await response.json();
// setAvatarUrl(data.url);
// For demo, just use local preview
const reader = new FileReader();
reader.onloadend = () => {
setAvatarUrl(reader.result);
};
reader.readAsDataURL(file);
};
const handleRemove = () => {
setAvatarUrl(null);
console.log('Avatar removed');
};
return (
<div className="profile">
<h1>User Profile</h1>
<AvatarUploader
currentAvatar={avatarUrl}
maxSizeMB={5}
onUpload={handleUpload}
onRemove={handleRemove}
/>
</div>
);
}Best Practices:
Common Pitfalls:
Enhancements:
Advanced Version with Drag and Drop:
import React, { useState, useRef } from 'react';
function AvatarUploaderWithDragDrop() {
const [preview, setPreview] = useState(null);
const [isDragging, setIsDragging] = useState(false);
const dropZoneRef = useRef(null);
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const files = e.dataTransfer.files;
if (files && files.length > 0) {
const file = files[0];
processFile(file);
}
};
const processFile = (file) => {
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result);
};
reader.readAsDataURL(file);
};
return (
// ...Real API Integration Example:
const handleUpload = async (file) => {
setIsLoading(true);
try {
const formData = new FormData();
formData.append('avatar', file);
formData.append('userId', currentUser.id);
const response = await fetch('/api/users/avatar', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
if (!response.ok) {
throw new Error('Upload failed');
}
const data = await response.json();
setAvatarUrl(data.avatarUrl);
} catch (error) {
setError('Failed to upload avatar. Please try again.');
} finally {
setIsLoading(false);
}
};Testing Considerations:
Real-World Use Cases:
This avatar uploader component provides a complete image upload experience with validation, preview, and error handling.
No files open
Click a file in the explorer to view