skip to Main Content

I want to create a form that uploads the images to Cloudinary and then gets the image’s URL and parses it into Supabase database, but I only want to upload the images when I click the Publish button (right now the image will auto upload when I selected it).

So I wondering how I can create a function for that purpose. Please help! Thank you.

Here is my code:

'use client';

import { useState, useEffect } from 'react'
import { useRouter, redirect } from 'next/navigation';
import { useUser } from '@/contexts/AuthContext';
import { Database } from '@/db_types'
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 { 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);


  if (!userDetails) {
    redirect('/login');
  }


  const uploadImage = async (e : any) => {
    const reader = new FileReader();
    reader.onloadend = async () => {
      setLoading(true);
    };
    if (!e.target.files || e.target.files.length === 0) {
      throw new Error('You must select an image to upload.')
    }

    const files = e.target.files[0]
    if (!files) return;
    const data = new FormData();
    data.append("file", files);
    data.append("upload_preset", "c_tags");
    const res = await fetch(`${process.env.NEXT_PUBLIC_CLOUDINARY_API}`,
      {
        method: "POST",
        body: data,
      }
    );
    const file = await res.json();
    setImageData({ ...imageData, images: file.secure_url });
    setLoading(false);
  };

  async function addPost({
    title,
    description,
    slug,
    images,
    user_id,
  }: {
    title: Posts['title']
    description: Posts['description']
    slug: Posts['slug']
    images: Posts['images']
    user_id: Posts['user_id'] 
  }) {
    try {
        setLoading(true)
        

        
        const updates = {
          title,
          description,
          slug,
          images : `${imageData.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/*"
          onChange={uploadImage}
        />

        <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"
            onClick={() => addPost({ title, description, slug, user_id, images })}
          >
            Publish
          </Button>
          </div>
        </form>
      </div>
    </div>
  );
}

2

Answers


  1. To upload the image after clicking on Publish button, you could simply move the uploading part inside addPost, aka remove uploadImage, and handle everything in one place, on submit.

    For that, set an imageInputRef:

    const imageInputRef = useRef<HTMLInputElement>(null);
    

    Add it to your upload input (notice the onChange is removed):

    <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}
    />
    

    Change your Publish button as below (notice the onClick is removed, and a type=submit is added):

    <Button type="sumbit" className="mt-5 bg-red-500">
      Publish
    </Button>
    

    And finally, remove uploadImage and change addPost to:

    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 file = imageInputRef.current.files[0];
        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,
        });
        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);
      }
    }
    
    Login or Signup to reply.
  2. I think you can do this by creating a separate state for storing the image until you click the publish button.

    const [imgState, setImgState] = useState(null);
    
    function onImgAdd(e){
    // for input type file
    setImgState(e.target.files[0])
    }
    
    function onPublishClick(){
    // use upload logic for the file, use
    uploadFunction(imgState)
    }
    
    

    This also gives you an easier way if you want to show a preview before publishing without having to actually upload it by creating a local url which is a common feature in a lot of web apps and you might end up needing it later on.

    // Its a simple url that you can add as src of an img tag
    const url = URL.createObjectURL(imgState);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search