如何在React中服务器操作提交表单后(不)重置表单?
在 React 中使用服务器操作提交表单时,你可能会遇到这样一个问题:如何在服务器操作执行后(不)重置表单。这取决于你在 React 之上使用的框架,表单可能会自动重置,也可能需要你手动重置。
在 React 中,表单的默认行为是在提交操作后自动重置。无论表单提交是否成功,都会发生这种情况。当你在 React 之上使用 Next.js(因为你需要一个框架来使用服务器操作),这种默认行为并不会改变。
在本教程中,我将展示如何在服务器操作执行后保持表单状态不变。但这种情况仅适用于服务器操作失败时。如果服务器操作成功,表单应按常规方式重置。
大家也看到了,我将文章标题定为“如何在 React 中服务器操作提交表单后(不)重置表单”,是因为在早期的 React 版本中,默认行为与此相反。这时,你需要在服务器操作后手动重置表单。
如何在 React 中不重置表单
接下来我将从一个示例开始,在这个示例中,用户可以通过在 Next.js 中使用带有服务器操作的表单来创建帖子。当用户提交表单时,数据会被发送到服务器:
import { createPost } from "../actions/create-post";
const PostCreateForm = () => {
return (
<form action={createPost}>
<label htmlFor="name">Name:</label>
<input name="name" id="name" />
<label htmlFor="content">Content:</label>
<textarea name="content" id="content" />
<button type="submit">Send</button>
</form>
);
};
该表单包含两个字段和一个提交按钮。当用户点击提交按钮时,会调用服务器操作,提取表单数据,并在数据库中创建一个帖子。如果表单提交成功,表单会自动重置:
"use server";
export const createPost = async (formData: FormData) => {
const data = {
name: formData.get("name"),
content: formData.get("content"),
};
if (!data.name || !data.content) {
throw new Error("Please fill in all fields");
}
// TODO: create post in database
};
但是,如果表单提交失败,原因是验证错误或数据库错误,用户就不得不重新输入数据,这显然不是很好的用户体验。我们可以通过在服务器操作中抛出一个错误来演示这一点,如果处理不当,这个错误将会导致应用程序崩溃。
通常,我们会使用 React 的 useActionState Hook 来处理服务器错误,并向用户显示一条消息。但是,这(目前)并不能阻止表单被重置:
"use client";
import { useActionState } from "react";
import { createPost } from "../actions/create-post";
const PostCreateForm = () => {
const [actionState, action] = useActionState(createPost, {
message: "",
});
return (
<form action={action}>
<label htmlFor="name">Name:</label>
<input name="name" id="name" />
<label htmlFor="content">Content:</label>
<textarea name="content" id="content" />
<button type="submit">Send</button>
{actionState.message}
</form>
);
};
在服务器端,我们通常会捕获错误并向客户端返回一条消息。这里我为了简化操作,将直接返回这条消息:
"use server";
type ActionState = {
message: string;
};
export const createPost = async (
_actionState: ActionState,
formData: FormData
) => {
const data = {
name: formData.get("name"),
content: formData.get("content"),
};
if (!data.name || !data.content) {
// throw new Error("Please fill in all fields");
return { message: "Please fill in all fields" };
}
// TODO: create post in database
return { message: "Post created" };
};
现在我们已经建立了基本设置。表单在服务器操作成功后会自动重置,如果服务器操作失败,则会显示错误消息。但在后者的情况下,表单数据会丢失。下面让我们通过防止表单在失败操作后重置来解决这个问题。
首先,如果表单提交失败,我们在服务器操作中返回表单数据:
"use server";
type ActionState = {
message: string;
payload?: FormData;
};
export const createPost = async (
_actionState: ActionState,
formData: FormData
) => {
const data = {
name: formData.get("name"),
content: formData.get("content"),
};
if (!data.name || !data.content) {
return {
message: "Please fill in all fields",
payload: formData,
};
}
// TODO: create post in database
return { message: "Post created" };
};
然后我们利用操作状态返回的表单数据,在服务器操作失败时,通过有条件地设置表单元素的默认值,来保持表单状态不变。
const PostCreateForm = () => {
const [actionState, action] = useActionState(createPost, {
message: "",
});
return (
<form action={action}>
<label htmlFor="name">Name:</label>
<input
name="name"
id="name"
defaultValue={(actionState.payload?.get("name") || "") as string}
/>
<label htmlFor="content">Content:</label>
<textarea
name="content"
id="content"
defaultValue={(actionState.payload?.get("content") || "") as string}
/>
<button type="submit">Send</button>
{actionState.message}
</form>
);
};
现在,在服务器操作失败后,表单数据将保持不变。用户可以更正表单数据并重新提交,而无需重新输入数据。在服务器操作成功时,表单将按常规方式重置。