next.js全栈开发一个重要的环节就是处理表单交互,比如用户登陆、新建数据等。
首先安装tailwindcss Form插件
npm install -D @tailwindcss/forms
使用Form插件,修改tailwind.config.js
/tailwind.config.js
const config: Config = {
...,
plugins: [
require('@tailwindcss/forms'),
],
}
然后新建一个Form表单组件
表单组件:/app/components/forms.tsx
'use client'
import { useFormState } from "react-dom";
import { SubmitButton } from "@/app/components/buttons";
const initialState = {
message: '',
}
export function UserForm(props: {action: any}) {
const {action} = props;
const [state, formAction] = useFormState(action, initialState);
return (
<div className="w-[200px] text-left justify-items-start leading-8">
<form action={formAction}>
<div>
<span>phone number:</span>
<input type="tel" name="phone" size={32} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
</div>
<div>
<span>password:</span>
<input type="email" name="email" size={32} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
</div>
<div aria-live="polite" className="text-xl">
{state?.message}
</div>
<div>
<SubmitButton className="text-xl bg-indigo-50 opacity-70 border border-gray-300 rounded-md shadow-md mt-2 p-2" />
</div>
</form>
</div>
);
}
useFormState是React18新增的Hook,可以根据某个表单动作的结果更新 state。原型参考
useFormState(action, initialState, permalink?)
其中action为表单处理函数,initialState为初始state,我们可以将数据包装在state中,通过state来在客户端组件和服务器组件之间传递通信。
action: /app/components/actions.ts
import { addNewUser } from "@/app/components/server";
import { redirect } from "next/navigation";
export async function createNewUser(prevState: any, formData:FormData) {
'use server'
const phone = formData.get('phone')?.toString();
if (!phone || phone === "") {
return {
'message': "phone can not be empty"
}
}
const email = formData.get('email')?.toString() || "";
let result;
try {
result = await addNewUser(phone, email);
console.log(result);
} catch(e) {
return {
'message': (e as Error).message
};
}
if (!result) {
return {
'message': "error"
}
}
redirect('/data');
}
处理表单的action中,第一个参数prevState是客户端组件传入的state,第二个参数是Form表单数据。
‘use server’表明该组件是运行于服务端。
处理表单逻辑或业务逻辑时发生任何异常,都可以通过返回一个state来告知客户端组件。例如
return {
'message': "phone number can not be empty"
}
处理成功后,可以通过redirect重定向至成功页面,也可以通过返回成功消息的state告知客户端组件。
新建页面
添加/app/data/add目录,然后在该目录下新建page.tsx文件
/app/data/add/page.tsx
import { UserForm } from "@/app/components/forms";
import { createNewUser } from "@/app/components/actions";
export default function Page() {
return (
<div className="flex flex-col w-full h-full items-center text-center overflow-hidden">
<div className="w-full">
Form
</div>
<div>
<UserForm action={createNewUser} />
</div>
</div>
);
}
效果预览
http://localhost:3000/data/add
插入数据库
在/app/components/server.ts中新增
import sqlite3 from "sqlite3";
sqlite3.verbose();
const db = new sqlite3.Database('./db.sqlite');
export async function addNewUser(phone: string, email: string): Promise<any> {
return new Promise((resolve, reject) => {
db.serialize(() => {
const stmt = db.prepare("INSERT INTO users(phone, email) VALUES (?, ?);")
stmt.bind(phone, email);
stmt.run((err) => {
if (err) {
reject(err);
}
});
stmt.finalize();
db.get("SELECT *, rowid FROM users WHERE rowid=LAST_INSERT_ROWID();", (err, row) => {
if (err) {
reject(err);
}
resolve(row);
});
});
});
}