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
To upload the image after clicking on Publish button, you could simply move the uploading part inside
addPost
, aka removeuploadImage
, and handle everything in one place, onsubmit
.For that, set an
imageInputRef
:Add it to your upload input (notice the
onChange
is removed):Change your Publish button as below (notice the
onClick
is removed, and atype=submit
is added):And finally, remove
uploadImage
and changeaddPost
to:I think you can do this by creating a separate state for storing the image until you click the publish button.
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.