前言
评论区/表单功能逻辑一般包括:输入校验 > 提交 > 提交成功提示 > 后台审核 > 前端展示。
本文将分享 React Hook Forms, Next.js 实现评论功能的步骤与方法。
效果图
技术栈
前端:React, Next.js, Tailwind CSS , TypeScript
后端-CMS:Sanity
工具安装
- 工具参考:React Hook Forms- react-hook-form.com/
- 根目录安装:
npm install react-hook-form
评论区表格:样式
{/* Comment Form */}
<hr className="max-w-lg my-5 mx-auto border border-yellow-500" />
<form className="flex flex-col p-5 max-w-2xl mx-auto mb-10">
<h3 className="text-sm text-yellow-500">Enjoyed this article?</h3>
<h4 className="text-3xl font-bold">Leave a comment below</h4>
<hr className="py-3 mt-2" />
<label className="block mb-5">
<span className="text-grey-700">Name</span>
<input
className="shadow border rounded py-2 px-3 form-input mt-1 block w-full ring-yellow-500 outline-none focus:ring"
placeholder="John"
type="text"
/>
</label>
<label className="block mb-5">
<span className="text-grey-700">Email</span>
<input
className="shadow border rounded py-2 px-3 form-input mt-1 block w-full ring-yellow-500 outline-none focus:ring"
placeholder="John"
type="text"
/>
</label>
<label className="block mb-5">
<span className="text-grey-700">Comment</span>
<textarea
className="shadow border rounded py-2 px-3 form-textarea mt-1 block w-full ring-yellow-500 outline-none focus:ring"
placeholder="John"
rows={8}
/>
</label>
</form>
输入校验功能
打开 [slug].tsx, 加入 react-hook-form 组件
import { useForm, SubmitHandler } from "react-hook-form";
定义表格域数据类型(TypeScript)
interface IFormInput {
_id: string;
name: string;
email: string;
comment: string;
}
function Post({ post }: Props) {
// Use react hook form
const {
register,
handleSubmit,
formState: { errors },
} = useForm<IFormInput>(); // Form will know it can only have the defined type
}
将表格与 react-hook-form 连接,实现:输入不合法提示
- 在 input 标签中添加
{...register("<域>"), {required: true}}
{/* Connect to React Hook Form, enable easy data pull */}
<input {...register("_id")} type="hidden" name="_id" value={post._id} />
<label className="">
<span className="">Name</span>
<input
{...register("name", { required: true })}
/>
</label>
{/* errors will return when field validation fails */}
<div className="flex flex-col p-5">
{errors.name && (
<span className="text-red-500">The Name Field is required</span>
)}
</div>
评论提交功能
设置表格提交按钮功能
{/* 表格提交功能 */}
<form
onSubmit={handleSubmit(onSubmit)}
className=""
>
<input
type="submit"
className="shadow bg-yellow-500 hover:bg-yellow-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded cursor-pointer"
/>
</form>
获取表格提交数据,将数据推送至API后台
function Post({ post }: Props) {
// Get Form Submit Data
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
await fetch("/api/createComment", {
method: "POST",
body: JSON.stringify(data),
})
.then(() => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
}
打开 /api > 新建 createComment.ts
-
- 根目录安装:
npm i @sanity/client
- 根目录安装:
import type { NextApiRequest, NextApiResponse } from "next";
import sanityClient from "@sanity/client";
const config = {
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
useCdn: process.env.NODE_ENV === "production",
token: process.env.SANITY_API_TOKEN,
};
const client = sanityClient(config);
获取 TOKEN,得到API数据库读写权限
- sanity.io > log in> API > Add API token
- Permission> Editor > Save
- 将TOKEN 复制到 /.env.local 文件:
SANITY_API_TOKEN=... - 重启 Next.js Server:
npm run dev
将前端数据传送至后台
export default async function createComment(
req: NextApiRequest,
res: NextApiResponse
) {
const { _id, name, email, comment } = JSON.parse(req.body);
try {
await client.create({
_type: "comment",
post: {
_type: "reference",
_ref: _id,
},
name,
email,
comment,
});
} catch (err) {
return res.status(500).json({ message: `Couldn't submit comment`, err });
}
console.log("Comment submitted");
res.status(200).json({ message: "Comment submitted" });
}
调整后台表单条目> /schemas > 新建 comment.js> 后台网址查看
- 表单报错:
Parsing error: Cannot find module '@babel/preset-env'
export default {
name: "comment",
title: "Comment",
type: "document",
fields: [
{
name: "name",
type: "string",
},
{
title: "Approved",
name: "approved",
type: "boolean",
decription: "Comments won't show on the site without approval",
},
{
name: "email",
type: "string",
},
{
name: "comment",
type: "text",
},
{
name: "post",
type: "reference",
to: [{ type: "post" }],
},
],
};
前端提交成功提示>获取后台审核过的评论
提交成功状态提示
- 运用React:useState
import { useState } from "react";
// To check submit state
const [submitted, setSubmitted] = useState(false);
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
await fetch("/api/createComment", {
method: "POST",
body: JSON.stringify(data),
})
.then(() => {
setSubmitted(true);
})
.catch((err) => {
setSubmitted(false);
});
};
// To display submit success message
{submitted? (<成功界面>):(<未提交界面>)}
前端获取后台审核后的评论
- 打开 /typings.d.ts,定义comment类型
export interface Post {
comments: Comment[];
}
export interface Comment {
approved: boolean;
comment: string;
email: string;
name: string;
post: {
_ref: string;
_type: string;
};
_createdAt: string;
_id: string;
_rev: string;
_type: string;
_updatedAt: string;
}
- 更新 getStaticProps,添加 comment
export const getStaticProps: GetStaticProps = async ({ params }) => {
const query = `*[_type =="post" && slug.current == $slug][0]{
_id,
_createdAt,
title,
author->{
name,
image
},
'comment': *[
_type== "comment" &&
post._ref ==^._id &&
approved == true],
description,
mainImage,
slug,
body
}`;
}
前端展示评论
{/* Comments */}
<div className="flex flex-col p-10 my-10 max-w-2xl mx-auto shadow-yellow-500 shadow space-y-2">
<h3 className="text-4xl">Comments</h3>
<hr className="pb-2" />
{post.comments.map((comment) => (
<div key={comment._id}>
<p>
<span className="text-yellow-500">{comment.name}: </span>
{comment.comment}
</p>
</div>
))}
</div>