Search code examples
reactjscloudinarysupabasenext.js13

Upload multiple images to Cloudinary and parses url to Supabase in Nextjs 13


I Want to Upload multiple images to Cloudinary and parses the URLs to Supabase in Nextjs 13 but when clicking Publish button, only 1 image is uploaded. I want to loop through all the media files and upload all the images selected and then parse all the URLs to the Supabse database column. How can I archive this. Please help! Thank you

Here is my code:

'use client';

import { useState, useEffect, useRef } from 'react'
import { useUser } from '@/contexts/AuthContext';
import { Database } from '@/db_types'
import { redirect } from 'next/navigation';

import Button from "@/components/shared/Button"
import Editor from "@/components/shared/Editor"

type Posts = Database['public']['Tables']['posts']['Row']

const initialState = {
  title: "",
  description: "",
  slug: "",
  images: "",
};

export default function AddPost() {

  const { user, userDetails , supabase } = useUser();
  const [loading, setLoading] = useState(true)
  const [title, setTitle] = useState<Posts['title']>(null)
  const [description, setDescription] = useState<Posts['description']>(null)
  const [slug, setSlug] = useState<Posts['slug']>(null)
  const [user_id] = useState<Posts['user_id']>(null)
  const [images, setImages] = useState<Posts['images']>(null)
  const [imageData, setImageData] = useState(initialState);
  const imageInputRef = useRef<HTMLInputElement>(null);

  if (user) {
    redirect('/');
  }


  async function addPost({
    title,
    description,
    slug,
  } : {
    title: Posts["title"];
    description: Posts["description"];
    slug: Posts["slug"];
    images: Posts['images'];
    user_id: Posts["user_id"];
  }) {
    try {
      if (!imageInputRef.current?.files || imageInputRef.current?.files.length === 0) {
        // You could set some error message in a state here.
        return;
      }
  
      setLoading(true);
  
      const data = new FormData();
      for (let i = 0; i < imageInputRef.current.files.length; i++){
        data.append("file", imageInputRef.current.files[i]);
      }
      data.append("upload_preset", "c_tags");
      
      const res = await fetch(`${process.env.NEXT_PUBLIC_CLOUDINARY_API}`, {
        method: "POST",
        body: data,
      });
      
      const returnedFile = await res.json();
  
      const newImageData = { ...imageData, images: returnedFile.secure_url };
      setImageData(newImageData);
      
      const updates = {
        title,
        description,
        slug,
        images: `${newImageData.images}`,
        created_at: new Date().toISOString(),
        user_id: userDetails?.id,
      };
  
      let { error } = await supabase.from("posts").upsert(updates);
      if (error) throw error;
      alert("Published!");
    } catch (error) {
      alert("Error updating the data!");
      console.log(error);
    } finally {
      setLoading(false);
    }
  }

 

  
  
  return (
    <div className="">
      <div>
      <form
          className="mt-3"
          onSubmit={(e) => {
            e.preventDefault();
            addPost({ title, description, slug, images, user_id })
          }}
        >
        <input
          id="images"
          type="file"
          className="relative block w-full appearance-none rounded-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
          accept="image/*"
          ref={imageInputRef}
          multiple
          />

        <label htmlFor="title">Title</label>
        <input
          id="title"
          type="text"
          className="relative block w-full appearance-none rounded-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
          value={title || ''}
          onChange={(e) => setTitle(e.target.value)}
        />
   
        
        <label htmlFor="Description">Content</label>
        <Editor
            description={description}
            setDescription={setDescription}
        />
    
        <label htmlFor="slug">Slug</label>
        <input
          id="slug"
          type="text"
          className="relative block w-full appearance-none rounded-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
          value={slug || ''}
          onChange={(e) => setSlug(e.target.value)}
        />
          <div>
          <Button
            className="mt-5 bg-red-500"
            type="submit"
          >
            Publish
          </Button>
          </div>
        </form>
      </div>
    </div>
  );
}

Solution

  • Here is the solution:

    for (let i = 0; i < imageInputRef.current.files.length; i++){
            const file = imageInputRef.current.files[i];
      
            const data = new FormData();
            data.append("file", file);
            data.append("upload_preset", "c_tags");
      
            const res = await fetch(`${process.env.NEXT_PUBLIC_CLOUDINARY_API}`, {
              method: "POST",
              body: data,
            });