March 17, 2025
O. Wolfson
This component handles image uploading in a React application. It supports uploading multiple files or external image URLs. Each image can have an optional caption, and users can reorder images via drag-and-drop. Files are uploaded to Supabase Storage and displayed using their public URLs.
The functionality is structured to allow users to upload, sort, and annotate images efficiently within a single interface.
bashnpx create-next-app@latest image-uploader
cd image-uploader
npm install @supabase/supabase-js uuid @dnd-kit/core @dnd-kit/sortable lucide-react
Create a Supabase client instance:
tsimport { createClient } from "@supabase/supabase-js";
const supabaseUrl = "https://your-supabase-url.supabase.co";
const supabaseKey = "your-supabase-anon-key";
const supabase = createClient(supabaseUrl, supabaseKey);
tsconst [uploading, setUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [photos, setPhotos] = useState<UploadedPhoto[]>([]);
const [imageUrl, setImageUrl] = useState<string>("");
Image files are validated and uploaded to Supabase Storage. Public URLs are used to render images in the UI.
tsconst allowedTypes = ["image/jpeg", "image/png", "image/webp"];
const maxSize = 5 * 1024 * 1024;
const uploadFiles = async (files: FileList | File[]) => {
const validFiles = Array.from(files).filter(
(file) => allowedTypes.includes(file.type) && file.size <= maxSize
);
if (validFiles.length === 0) return;
const uploadedPhotos: UploadedPhoto[] = [];
for (const file of validFiles) {
const fileName = `${uuidv4()}_${file.name}`;
const { error: uploadError } = await supabase.storage
.from("locations")
.upload(fileName, file);
if (uploadError) throw new Error(uploadError.message);
const { data } = supabase.storage.from("locations").getPublicUrl(fileName);
uploadedPhotos.push({ url: data.publicUrl, : });
}
( [...prev, ...uploadedPhotos]);
};
External images can be added by entering a valid URL.
tsconst handleAddUrl = () => {
try {
new URL(imageUrl);
setPhotos((prev) => [...prev, { url: imageUrl, caption: "" }]);
setImageUrl("");
} catch {
setError("Invalid URL format.");
}
};
@dnd-kit
is used to enable drag-and-drop reordering of image cards.
tsx<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
<SortableContext
items={photos.map((_, i) => `photo-${i}`)}
strategy={verticalListSortingStrategy}
>
<div className="grid grid-cols-2 gap-4">
{photos.map((photo, index) => (
<SortablePhoto
key={`photo-${index}`}
id={`photo-${index}`}
index={index}
photo={photo}
onRemove={() => removePhoto(index)}
onMoveUp={() => movePhoto(index, index - 1)}
onMoveDown={() => movePhoto(index, index + 1)}
onCaptionChange={(caption) => handleCaptionChange(index, caption)}
isFirst={index === 0}
isLast={index === photos.length - 1}
/>
))}
</div>
</SortableContext>
</DndContext>
Each image has an input for a caption and buttons to move or delete the item. The order updates immediately in state.
Click here to view the full code.
This component handles common image uploading needs in React projects. It integrates file validation, reordering, and cloud storage access with a straightforward interface. Suitable for use cases like image galleries, product listings, or content upload forms.