2025-03-17 Web Development
Building a React Image Uploader with Supabase Storage By O. Wolfson
Introduction
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.
Key Features
Upload multiple image files (JPG, PNG, WEBP)
Drag-and-drop reordering
Optional captions per image
External image URL input
File validation (max 5MB, allowed types)
Supabase Storage integration
Remove image functionality
Setting Up the Project
bash Copy
npx create-next-app@latest image-uploader
cd image-uploader
npm install @supabase/supabase-js uuid @dnd-kit/core @dnd-kit/sortable lucide-react
Connecting to Supabase
Create a Supabase client instance:
ts Copy
import { createClient } from "@supabase/supabase-js" ;
const supabaseUrl = "https://your-supabase-url.supabase.co" ;
const supabaseKey = "your-supabase-anon-key" ;
const supabase = createClient (supabaseUrl, supabaseKey);
Component State
ts Copy
const [uploading, setUploading] = useState (false );
const [error, setError] = useState<string | null >(null );
const [photos, setPhotos] = useState<UploadedPhoto []>([]);
const [imageUrl, setImageUrl] = useState<string >("" );
Uploading Files
Image files are validated and uploaded to Supabase Storage. Public URLs are used to render images in the UI.
ts Copy
const 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 , caption : "" });
}
setPhotos ((prev ) => [...prev, ...uploadedPhotos]);
};
Adding External URLs
External images can be added by entering a valid URL.
ts Copy
const handleAddUrl = ( ) => {
try {
new URL (imageUrl);
setPhotos ((prev ) => [...prev, { url : imageUrl, caption : "" }]);
setImageUrl ("" );
} catch {
setError ("Invalid URL format." );
}
};
Drag-and-Drop Ordering
@dnd-kit
is used to enable drag-and-drop reordering of image cards.
tsx Copy
<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 >
Editable Captions & Controls
Each image has an input for a caption and buttons to move or delete the item. The order updates immediately in state.
Full Code
Click here to view the full code.
Conclusion
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.