我想创建一个表单,将图片上传到 Cloudinary,然后获取图片的 URL 并将其解析到 Supabase 数据库中,但我只想在单击 Publish 按钮时上传图片(现在图片将自动上传当我选择它时)。
所以我想知道如何为此目的创建一个函数。请帮忙!谢谢。
这是我的代码:
'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>
);
}
从
onClick
中删除Button
方法并声明它的类型submit
:
<Button className="mt-5 bg-red-500" type="submit">
Publish
</Button>
您可以将图片上传逻辑从 uploadImage 函数移动到 addPost 函数。
// ...
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const onImageSelect = (e: any) => {
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;
setSelectedFile(files);
};
async function uploadImage() {
if (!selectedFile) return;
const data = new FormData();
data.append('file', selectedFile);
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);
}
// ...
return (
<div className="">
{/* ... */}
<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={onImageSelect}
/>
{/* ... */}
</div>
);
现在,修改 addPost 函数,在添加帖子之前调用 uploadImage 函数:
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);
// Upload the image before adding a post
await uploadImage();
// ...
} catch (error) {
// ...
} finally {
// ...
}
}
要在点击“发布”按钮后上传图片,您只需将上传部分移到
addPost
内,也就是删除uploadImage
,并在一个地方处理所有内容,在submit
.
为此,设置一个
imageInputRef
:
const imageInputRef = useRef<HTMLInputElement>(null);
将其添加到您的上传输入(注意
onChange
被删除):
<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}
/>
更改您的发布按钮如下(注意删除
onClick
,并添加type=submit
):
<Button type="sumbit" className="mt-5 bg-red-500">
Publish
</Button>
最后,删除
uploadImage
并将addPost
更改为:
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);
}
}
我认为你可以通过创建一个单独的状态来存储图像,直到你点击发布按钮。
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)
}
如果您想在发布前显示预览而不必通过创建本地 url 实际上传它,这也为您提供了一种更简单的方法,这是许多网络应用程序中的常见功能,您以后可能最终需要它。
// Its a simple url that you can add as src of an img tag
const url = URL.createObjectURL(imgState);