<form>

内置的浏览器 <form> 组件(built-in browser <form> component)可让你创建交互式控件,用于提交信息。

<form action={search}>
<input name="query" />
<button type="submit">搜索</button>
</form>

参考

<form>

要创建用于提交信息的交互式控件,请渲染内置的浏览器 <form> 组件(built-in browser <form> component)。

<form action={search}>
<input name="query" />
<button type="submit">搜索</button>
</form>

查看更多示例。

属性

<form> 支持所有通用元素属性。

action:一个 URL 或函数。当将 URL 传递给 action 时,表单将表现得像 HTML 表单组件一样。当将函数传递给 action 时,该函数将在一个 Transition 中处理表单提交,遵循Action 属性模式。传递给 action 的函数可以是异步的,并且会以单个参数被调用,该参数包含已提交表单的表单数据action 属性可以被 <button><input type="submit"><input type="image"> 组件上的 formAction 属性覆盖。

注意事项

  • 当函数被传递给 actionformAction 时,无论 method 属性的值是什么,HTTP 方法都将是 POST。

用法

使用事件处理函数处理表单提交

将一个函数传递给 onSubmit 事件处理函数,以便在表单提交时运行代码。默认情况下,浏览器会将表单数据发送到当前 URL 并刷新页面,因此请调用 e.preventDefault() 来覆盖这种行为。

这个示例使用 new FormData(e.target) 读取已提交的值,它会按每个字段的 name 收集所有字段。这使这些输入保持为非受控。如果你改为使用 state 控制输入,那么在提交时应从该 state 中读取,而不是从 FormData 中读取。

export default function Search() {
  function handleSubmit(e) {
    // 阻止浏览器重新加载页面
    e.preventDefault();

    // 读取表单数据
    const form = e.target;
    const formData = new FormData(form);
    const query = formData.get("query");
    alert(`You searched for '${query}'`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="query" />
      <button type="submit">Search</button>
    </form>
  );
}

Note

使用 onSubmit 读取表单数据在 React 的每个版本中都有效,并且可以直接访问提交事件,因此你可以调用 e.preventDefault() 并自行读取数据。将函数传递给 action 属性则会在 Transition 中运行提交。然后 React 会跟踪待处理状态,将抛出的错误发送到最近的错误边界,并使表单能够与 useActionStateuseOptimistic 配合工作。action 也可以是一个服务器函数,而 onSubmit 不支持这一点。

使用 action 属性处理表单提交

将一个函数传递给表单的 action 属性,以便在表单提交时运行该函数。[formData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) 将作为参数传递给函数,因此你可以访问表单提交的数据。这与传统的 HTML action 不同,后者只接受 URL。与 onSubmit 不同,action 会在 Transition 中运行,并且不需要调用 e.preventDefault()。在 action 函数成功后,表单中所有非受控字段元素都会被重置。

export default function Search() {
  function search(formData) {
    const query = formData.get("query");
    alert(`你搜索了 '${query}'`);
  }
  return (
    <form action={search}>
      <input name="query" />
      <button type="submit">搜索</button>
    </form>
  );
}

使用 Server Function 处理表单提交

渲染一个带有输入框和提交按钮的 <form>。将一个 Server Function(一个标记了'use server' 的函数)传递给 form 的 action 属性,以便在表单提交时运行该函数。

将 Server Function 传递给 <form action>,可以让用户在未启用 JavaScript 或代码尚未加载之前提交表单。这对网络连接慢、设备性能较弱或禁用 JavaScript 的用户很有帮助,其工作方式类似于将 URL 传递给 action 属性时的表单行为。

你可以使用隐藏表单字段向 <form> 的 action 提供数据。Server Function 将以隐藏表单字段数据作为 FormData 的实例被调用。

import { updateCart } from './lib.js';

function AddToCart({productId}) {
async function addToCart(formData) {
'use server'
const productId = formData.get('productId')
await updateCart(productId)
}
return (
<form action={addToCart}>
<input type="hidden" name="productId" value={productId} />
<button type="submit">添加到购物车</button>
</form>

);
}

如果不使用隐藏表单字段向 <form> 的 action 提供数据,你也可以调用 bind 方法来为其提供额外参数。这会将一个新参数(productId)绑定到函数上,除此之外还有作为参数传递给函数的 formData

import { updateCart } from './lib.js';

function AddToCart({productId}) {
async function addToCart(productId, formData) {
"use server";
await updateCart(productId)
}
const addProductToCart = addToCart.bind(null, productId);
return (
<form action={addProductToCart}>
<button type="submit">添加到购物车</button>
</form>
);
}

<form>Server Component 渲染,并且有一个Server Function 被传递给 <form>action 属性时,该表单会进行渐进增强

在表单提交期间显示待处理状态

要在表单提交时显示待处理状态,你可以在 <form> 中渲染的组件里调用 useFormStatus Hook,并读取返回的 pending 属性。

这里,我们使用 pending 属性来表示表单正在提交。

import { useFormStatus } from "react-dom";
import { submitForm } from "./actions.js";

function Submit() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "提交中..." : "提交"}
    </button>
  );
}

function Form({ action }) {
  return (
    <form action={action}>
      <Submit />
    </form>
  );
}

export default function App() {
  return <Form action={submitForm} />;
}

要了解更多关于 useFormStatus Hook 的内容,请参见参考文档

乐观更新表单数据

useOptimistic Hook 提供了一种在后台操作(如网络请求)完成之前,乐观更新用户界面的方式。在表单场景中,这种技术有助于让应用感觉更响应迅速。当用户提交表单时,界面会立即根据预期结果进行更新,而不是等待服务器响应后再反映变化。

例如,当用户在表单中输入消息并点击“发送”按钮时,useOptimistic Hook 允许该消息立即出现在列表中,并带有“发送中…”标签,甚至在消息真正发送到服务器之前也是如此。这种“乐观”做法会给人一种速度快、响应快的印象。随后表单会在后台尝试真正发送该消息。一旦服务器确认消息已接收,“发送中…”标签就会被移除。

import { useOptimistic, useState, useRef } from "react";
import { deliverMessage } from "./actions.js";

function Thread({ messages, sendMessage }) {
  const formRef = useRef();
  async function formAction(formData) {
    addOptimisticMessage(formData.get("message"));
    formRef.current.reset();
    await sendMessage(formData);
  }
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [
      ...state,
      {
        text: newMessage,
        sending: true
      }
    ]
  );

  return (
    <>
      {optimisticMessages.map((message, index) => (
        <div key={index}>
          {message.text}
          {!!message.sending && <small>(发送中...)</small>}
        </div>
      ))}
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="你好!" />
        <button type="submit">发送</button>
      </form>
    </>
  );
}

export default function App() {
  const [messages, setMessages] = useState([
    { text: "你好呀!", sending: false, key: 1 }
  ]);
  async function sendMessage(formData) {
    const sentMessage = await deliverMessage(formData.get("message"));
    setMessages((messages) => [...messages, { text: sentMessage }]);
  }
  return <Thread messages={messages} sendMessage={sendMessage} />;
}

处理表单提交错误

在某些情况下,由 <form>action 属性调用的函数会抛出错误。你可以通过将 <form> 包裹在 Error Boundary 中来处理这些错误。如果由 <form>action 属性调用的函数抛出错误,则会显示错误边界的回退内容。

import { ErrorBoundary } from "react-error-boundary";

export default function Search() {
  function search() {
    throw new Error("搜索错误");
  }
  return (
    <ErrorBoundary
      fallback={<p>提交表单时出错了</p>}
    >
      <form action={search}>
        <input name="query" />
        <button type="submit">搜索</button>
      </form>
    </ErrorBoundary>
  );
}

在没有 JavaScript 的情况下显示表单提交错误

在渐进增强中,要在 JavaScript 包加载之前显示表单提交错误消息,需要满足以下条件:

  1. <form>Client Component 渲染
  2. 传递给 <form>action 属性的函数是一个 Server Function
  3. 使用 useActionState Hook 来显示错误消息

useActionState 接受两个参数:一个 Server Function 和一个初始状态。useActionState 返回两个值:一个状态变量和一个 action。useActionState 返回的 action 应传递给表单的 action 属性。useActionState 返回的状态变量可用于显示错误消息。传递给 useActionState 的 Server Function 的返回值将用于更新该状态变量。

import { useActionState } from "react";
import { signUpNewUser } from "./api";

export default function Page() {
  async function signup(prevState, formData) {
    "use server";
    const email = formData.get("email");
    try {
      await signUpNewUser(email);
      alert(`已添加 "${email}"`);
    } catch (err) {
      return err.toString();
    }
  }
  const [message, signupAction] = useActionState(signup, null);
  return (
    <>
      <h1>注册我的新闻邮件</h1>
      <p>使用同一个邮箱注册两次即可看到错误</p>
      <form action={signupAction} id="signup-form">
        <label htmlFor="email">邮箱: </label>
        <input name="email" id="email" placeholder="react@example.com" />
        <button>注册</button>
        {!!message && <p>{message}</p>}
      </form>
    </>
  );
}

了解更多关于从表单 action 更新状态的信息,请参阅 useActionState 文档

处理多种提交类型

可以将表单设计为根据用户按下的按钮来处理多种提交操作。表单中的每个按钮都可以通过设置 formAction 属性来关联不同的 action 或行为。

当用户点击某个特定按钮时,表单会被提交,并执行该按钮属性和 action 所定义的相应操作。例如,表单可能默认提交一篇文章供审核,但有一个单独的按钮将 formAction 设置为把文章保存为草稿。

export default function Search() {
  function publish(formData) {
    const content = formData.get("content");
    const button = formData.get("button");
    alert(`'${content}' 已使用 '${button}' 按钮发布`);
  }

  function save(formData) {
    const content = formData.get("content");
    alert(`你的 '${content}' 草稿已保存!`);
  }

  return (
    <form action={publish}>
      <textarea name="content" rows={4} cols={40} />
      <br />
      <button type="submit" name="button" value="submit">发布</button>
      <button formAction={save}>保存草稿</button>
    </form>
  );
}