Adding multiple image upload functionality to a web application enhances user experience by allowing the association of several images with a single entity—such as a “sketch” in a design app. This guide walks you through implementing this feature in a React/Next.js frontend with a backend that stores images as an array, covering state management, file handling, UI updates, and API integration.
Step 1: Set Up the Frontend Component
Start by creating or updating a component (e.g., UploadSketchImages) to handle multiple image uploads. Use React’s useState to manage an array of image previews.
import { useState } from ‘react’;
const UploadSketchImages = () => {
const [imagePreviews, setImagePreviews] = useState([]);
const MAX_IMAGES = 5;
return (
<div>
<input type=”file” multiple accept=”image/*” onChange={handleFileChange} />
{/* Additional UI elements */}
</div>
);
};
export default UploadSketchImages;
Define an interface for image objects to track both the file and its preview URL:
interface ImagePreview {
file: File | null;
preview: string;
}
Step 2: Handle File Selection with an Array
Create a handleFileChange function to process multiple files into the imagePreviews array, enforcing the maximum limit.
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
if (!files) return;
const newPreviews = Array.from(files).map((file) => ({
file,
preview: URL.createObjectURL(file),
}));
if (imagePreviews.length + newPreviews.length > MAX_IMAGES) {
alert(`Maximum of ${MAX_IMAGES} images allowed.`);
return;
}
setImagePreviews((prev) => […prev, …newPreviews]);
};
Here, Array.from(files) converts the FileList into an array, and URL.createObjectURL generates a temporary URL for previewing each image. The spread operator (…prev, …newPreviews) appends new images to the existing array without overwriting it.
Step 3: Display Images in a Tiled Layout
Render the imagePreviews array as a tiled preview, allowing users to see their selections.
<div className=”grid grid-cols-5 gap-4″>
{imagePreviews.map((image, index) => (
<div key={index} className=”relative w-16 h-16″>
<img src={image.preview} alt={`Preview ${index}`} className=”w-full h-full object-cover” />
</div>
))}
</div>
Use CSS (e.g., Tailwind’s grid-cols-5) to create a responsive tile layout. Each image is displayed as a thumbnail based on its preview URL.
Step 4: Add Deletion Functionality
Enable users to remove images by adding a delete button to each thumbnail, updating the array accordingly.
const removeImage = (index: number) => {
setImagePreviews((prev) => prev.filter((_, i) => i !== index));
};
// In the render:
<div className=”grid grid-cols-5 gap-4″>
{imagePreviews.map((image, index) => (
<div key={index} className=”relative w-16 h-16″>
<img src={image.preview} alt={`Preview ${index}`} className=”w-full h-full object-cover” />
<button
onClick={() => removeImage(index)}
className=”absolute top-0 right-0 bg-red-500 text-white w-5 h-5 rounded-full flex items-center justify-center”
>
✕
</button>
</div>
))}
</div>
const removeImage = (index: number) => {
setImagePreviews((prev) => prev.filter((_, i) => i !== index));
};
// In the render:
<div className=”grid grid-cols-5 gap-4″>
{imagePreviews.map((image, index) => (
<div key={index} className=”relative w-16 h-16″>
<img src={image.preview} alt={`Preview ${index}`} className=”w-full h-full object-cover” />
<button
onClick={() => removeImage(index)}
className=”absolute top-0 right-0 bg-red-500 text-white w-5 h-5 rounded-full flex items-center justify-center”
>
✕
</button>
</div>
))}
</div>