useFormState - This feature is available in the latest Canary

Canary

useFormState Hook 当前仅在 React Canary 与 experimental 渠道中可用。请点此了解更多关于 React 发布渠道 的信息。此外,需要一款完全支持 React 服务器组件 特性的框架才可以使用 useFormState 的所有特性。

useFormState 是一个可以根据某个表单动作的结果更新 state 的 Hook。

const [state, formAction] = useFormState(fn, initialState, permalink?);

参考

useFormState(action, initialState, permalink?)

在组件的顶层调用 useFormState 即可创建一个随 表单动作被调用 而更新的 state。在调用 useFormState 时在参数中传入现有的表单动作函数以及一个初始状态,它就会返回一个新的 action 函数和一个 form state 以供在 form 中使用。这个新的 form state 也会作为参数传入提供的表单动作函数。

import { useFormState } from "react-dom";

async function increment(previousState, formData) {
return previousState + 1;
}

function StatefulForm({}) {
const [state, formAction] = useFormState(increment, 0);
return (
<form>
{state}
<button formAction={formAction}>+1</button>
</form>
)
}

form state 是一个只在表单被提交触发 action 后才会被更新的值。如果该表单没有被提交,该值会保持传入的初始值不变。

如果配合 Server Action 一起使用,useFormState 允许与表单交互的服务器的返回值在 hydration 完成前显示。

请参阅下方更多示例

参数

  • fn:当按钮被按下或者表单被提交时触发的函数。当函数被调用时,该函数会接收到表单的上一个 state(初始值为传入的 initialState 参数,否则为上一次执行完该函数的结果)作为函数的第一个参数,余下参数为普通表单动作接到的参数。
  • initialState:state 的初始值。任何可序列化的值都可接收。当 action 被调用一次后该参数会被忽略。
  • 可选 permalink: A string containing the unique page URL that this form modifies. For use on pages with dynamic content (eg: feeds) in conjunction with progressive enhancement: if fn is a server action and the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL, rather than the current page’s URL. Ensure that the same form component is rendered on the destination page (including the same action fn and permalink) so that React knows how to pass the state through. Once the form has been hydrated, this parameter has no effect.

返回值

useFormState 返回一个包含两个值的数组:

  1. 当前的 state。第一次渲染期间,该值为传入的 initialState 参数值。在 action 被调用后该值会变为 action 的返回值。
  2. 一个新的 action 函数用于在你的 form 组件的 action 参数或表单中任意一个 button 组件的 formAction 参数中传递。

注意

  • 在支持 React 服务器组件的框架中使用该功能时,useFormState 允许表单在服务器渲染阶段时获得部分交互性。当不使用服务器组件时,它的特性与本地 state 相同。
  • 与直接通过表单动作调用的函数不同,传入 useFormState 的函数被调用时,会多传入一个代表 state 的上一个值或初始值的参数作为该函数的第一个参数。

用法

使用某个表单动作返回的信息

在组件的顶层调用 useFormState 以获取上一次表单被提交时触发的 action 的返回值。

import { useFormState } from 'react-dom';
import { action } from './actions.js';

function MyComponent() {
const [state, formAction] = useFormState(action, null);
// ...
return (
<form action={formAction}>
{/* ... */}
</form>
);
}

useFormState 返回一个包含两个值的数组:

  1. 该表单的 当前 state,初始值为提供的 初始 state,当表单被提交后则改为传入的 action 的返回值。
  2. 传入 <form> 标签的 action 属性的 新 action

表单被提交后,传入的 action 函数会被执行。返回值将会作为该表单的新的 当前 state

传入的 action 接受到的第一个参数将会变为该表单的 当前 state。当表单第一次被提交时将会传入提供的 初始 state,之后都将传入上一次调用 action 函数的返回值。余下参数与未使用 useFormState 前接受的参数别无二致[1]

function action(currentState, formData) {
// ...
return 'next state';
}

提交表单后展示信息

1示例 2 个挑战:
展示表单错误

将 action 包裹进 useFormState 即可展示诸如错误信息或 Server Action 返回的 toast 等信息。

import { useState } from "react";
import { useFormState } from "react-dom";
import { addToCart } from "./actions.js";

function AddToCartForm({itemID, itemTitle}) {
  const [message, formAction] = useFormState(addToCart, null);
  return (
    <form action={formAction}>
      <h2>{itemTitle}</h2>
      <input type="hidden" name="itemID" value={itemID} />
      <button type="submit">加入购物车</button>
      {message}
    </form>
  );
}

export default function App() {
  return (
    <>
      <AddToCartForm itemID="1" itemTitle="JavaScript:权威指南" />
      <AddToCartForm itemID="2" itemTitle="JavaScript:优点荟萃" />
    </>
  )
}

疑难解答

我的 action 无法再获取提交的 form data 了

当使用 useFormState 包裹 action 时,第一个参数变为了 form 的当前 state,提交的表单数据被顺移到了第二个参数中,与直接使用表单动作是不同的。

function action(currentState, formData) {
// ...
}

译注:

[1] 这里的意思是原来的第一个参数被顺移为第二个参数,第二个参数被顺移为第三个参数,以此类推