主分支
分支
主分支 (6.23.1)开发分支
版本
6.23.1v4/5.xv3.x
表单
在本页

<Form>

类型声明
declare function Form(props: FormProps): React.ReactElement;

interface FormProps
  extends React.FormHTMLAttributes<HTMLFormElement> {
  method?: "get" | "post" | "put" | "patch" | "delete";
  encType?:
    | "application/x-www-form-urlencoded"
    | "multipart/form-data"
    | "text/plain";
  action?: string;
  onSubmit?: React.FormEventHandler<HTMLFormElement>;
  fetcherKey?: string;
  navigate?: boolean;
  preventScrollReset?: boolean;
  relative?: "route" | "path";
  reloadDocument?: boolean;
  replace?: boolean;
  state?: any;
  unstable_viewTransition?: boolean;
}

Form 组件是围绕普通 HTML form 的包装器,它模拟浏览器进行客户端路由和数据变异。它不是一个像你在 React 生态系统中习惯的那样的表单验证/状态管理库(为此,我们建议使用浏览器的内置 HTML 表单验证 和后端服务器上的数据验证)。

此功能仅在使用数据路由器时有效,请参阅 选择路由器

import { Form } from "react-router-dom";

function NewEvent() {
  return (
    <Form method="post" action="/events">
      <input type="text" name="title" />
      <input type="text" name="description" />
      <button type="submit">Create</button>
    </Form>
  );
}

确保你的输入有名称,否则 FormData 将不包含该字段的值。

所有这些都将触发对任何渲染的 useNavigation 钩子的状态更新,以便你可以在异步操作进行中构建挂起指示器和乐观 UI。

如果表单感觉不像导航,你可能需要 useFetcher.

action

表单将提交到的 URL,就像 HTML form action 一样。唯一的区别是默认操作。对于 HTML 表单,它默认为完整 URL。对于 <Form>,它默认为上下文中最近路由的相对 URL。

考虑以下路由和组件

function ProjectsLayout() {
  return (
    <>
      <Form method="post" />
      <Outlet />
    </>
  );
}

function ProjectsPage() {
  return <Form method="post" />;
}

<DataBrowserRouter>
  <Route
    path="/projects"
    element={<ProjectsLayout />}
    action={ProjectsLayout.action}
  >
    <Route
      path=":projectId"
      element={<ProjectsPage />}
      action={ProjectsPage.action}
    />
  </Route>
</DataBrowserRouter>;

如果当前 URL 是 "/projects/123",则子路由 ProjectsPage 中的表单将具有你预期的默认操作:"/projects/123"。在这种情况下,路由是最深匹配的路由,<Form> 和普通 HTML 表单的结果相同。

但是 ProjectsLayout 中的表单将指向 "/projects",而不是完整 URL。换句话说,它指向渲染表单的路由的 URL 的匹配段。

这有助于可移植性以及表单与其操作处理程序的共置,如果你在路由模块中添加了一些约定。

如果你需要发布到不同的路由,则添加一个 action 属性

<Form action="/projects/new" method="post" />

另请参阅

请参阅 useResolvedPath 文档中的 Splat 路径 部分,了解 future.v7_relativeSplatPath 未来标志对 splat 路由中相对 useNavigate() 行为的影响。

method

这决定了要使用的 HTTP 动词。与普通 HTML form method 相同,除了它还支持 "put"、"patch" 和 "delete",以及 "get" 和 "post"。默认值为 "get"。

GET 提交

默认方法是 "get"。GET 提交不会调用操作。GET 提交与普通导航(用户点击链接)相同,除了用户可以从表单中提供要转到 URL 的搜索参数。

<Form method="get" action="/products">
  <input
    aria-label="search products"
    type="text"
    name="q"
  />
  <button type="submit">Search</button>
</Form>

假设用户输入 "running shoes" 并提交表单。React Router 模拟浏览器,并将表单序列化为 URLSearchParams,然后将用户导航到 "/products?q=running+shoes"。这就像你作为开发人员渲染了 <Link to="/products?q=running+shoes"> 一样,但你让用户动态地提供查询字符串。

你的路由加载器可以通过从 request.url 创建一个新的 URL 来最方便地访问这些值,然后加载数据。

<Route
  path="/products"
  loader={async ({ request }) => {
    let url = new URL(request.url);
    let searchTerm = url.searchParams.get("q");
    return fakeSearchProducts(searchTerm);
  }}
/>

Mutation 提交

所有其他方法都是 "mutation 提交",这意味着你打算使用 POST、PUT、PATCH 或 DELETE 更改数据。请注意,普通 HTML 表单仅支持 "post" 和 "get",我们也倾向于坚持使用这两个方法。

当用户提交表单时,React Router 将匹配 action 到应用程序的路由,并使用序列化的 FormData 调用 <Route action>。当操作完成后,页面上的所有加载器数据将自动重新验证,以使你的 UI 与你的数据保持同步。

该方法将在调用的路由操作中的 request.method 上可用。你可以使用它来指示你的数据抽象提交的意图。

<Route
  path="/projects/:id"
  element={<Project />}
  loader={async ({ params }) => {
    return fakeLoadProject(params.id);
  }}
  action={async ({ request, params }) => {
    switch (request.method) {
      case "PUT": {
        let formData = await request.formData();
        let name = formData.get("projectName");
        return fakeUpdateProject(name);
      }
      case "DELETE": {
        return fakeDeleteProject(params.id);
      }
      default: {
        throw new Response("", { status: 405 });
      }
    }
  }}
/>;

function Project() {
  let project = useLoaderData();

  return (
    <>
      <Form method="put">
        <input
          type="text"
          name="projectName"
          defaultValue={project.name}
        />
        <button type="submit">Update Project</button>
      </Form>

      <Form method="delete">
        <button type="submit">Delete Project</button>
      </Form>
    </>
  );
}

如你所见,两种表单都提交到相同的路由,但你可以使用 request.method 来分支你打算做什么。操作完成后,loader 将重新验证,UI 将自动与新数据同步。

你可以通过指定 <Form navigate={false}> 来告诉表单跳过导航并使用内部的 fetcher。这本质上是 useFetcher() + <fetcher.Form> 的简写,你不在乎结果数据,只想要启动提交并通过 useFetchers() 访问挂起状态。

fetcherKey

当使用非导航 Form 时,你也可以选择通过 <Form navigate={false} fetcherKey="my-key"> 指定你自己的 fetcher 密钥。

replace

指示表单替换历史记录堆栈中的当前条目,而不是推送新条目。

<Form replace />

默认行为取决于表单行为

  • method=get 表单默认为 false
  • 提交方法取决于 formActionaction 行为
    • 如果你的 action 抛出异常,则它将默认为 false
    • 如果你的 action 重定向到当前位置,则它将默认为 true
    • 如果你的 action 重定向到其他位置,则它将默认为 false
    • 如果你的 formAction 是当前位置,则它将默认为 true
    • 否则它将默认为 false

我们发现对于 get,你通常希望用户能够点击 "back" 来查看之前的搜索结果/过滤器等。但对于其他方法,默认值为 true 以避免 "你确定要重新提交表单吗?" 提示。请注意,即使 replace={false},React Router 也不会 在点击后退按钮且方法为 post、put、patch 或 delete 时重新提交表单。

换句话说,这实际上只对 GET 提交有用,并且你希望避免后退按钮显示之前的结果。

relative

默认情况下,路径相对于路由层次结构,因此 .. 将向上移动一个 Route 层级。偶尔,你可能会发现你拥有不合理嵌套的匹配 URL 模式,并且你更喜欢使用相对路径路由。你可以使用 <Form to="../some/where" relative="path"> 选择此行为。

reloadDocument

指示表单跳过 React Router 并使用浏览器的内置行为提交表单。

<Form reloadDocument />

建议使用 <form>,这样你就可以获得默认和相对 action 的好处,但除此之外,它与普通 HTML 表单相同。

如果没有像 Remix 这样的框架,或者你自己的服务器处理对路由的发布,这将不是很有用。

另请参阅

state

state 属性可用于为新位置设置一个有状态的值,该值存储在 历史记录状态 中。随后可以通过 useLocation() 访问此值。

<Form
  method="post"
  action="new-path"
  state={{ some: "value" }}
/>

你可以在 "new-path" 路由上访问此状态值

let { state } = useLocation();

preventScrollReset

如果您正在使用 <ScrollRestoration>,这可以防止表单操作重定向到新位置时滚动位置重置到窗口顶部。

<Form method="post" preventScrollReset={true} />

另请参阅:<Link preventScrollReset>

unstable_viewTransition

unstable_viewTransition 属性通过将最终状态更新包装在 document.startViewTransition() 中,为此次导航启用 视图转换。如果您需要为此视图转换应用特定样式,您还需要利用 unstable_useViewTransitionState()

请注意,此 API 被标记为不稳定,可能会在没有重大版本的情况下发生重大更改。

示例

待办事项:更多示例

大型列表过滤

GET 提交的一个常见用例是过滤大型列表,例如电子商务和旅行预订网站。

function FilterForm() {
  return (
    <Form method="get" action="/slc/hotels">
      <select name="sort">
        <option value="price">Price</option>
        <option value="stars">Stars</option>
        <option value="distance">Distance</option>
      </select>

      <fieldset>
        <legend>Star Rating</legend>
        <label>
          <input type="radio" name="stars" value="5" />{" "}
          ★★★★★
        </label>
        <label>
          <input type="radio" name="stars" value="4" /> ★★★★
        </label>
        <label>
          <input type="radio" name="stars" value="3" /> ★★★
        </label>
        <label>
          <input type="radio" name="stars" value="2" /> ★★
        </label>
        <label>
          <input type="radio" name="stars" value="1" /> ★
        </label>
      </fieldset>

      <fieldset>
        <legend>Amenities</legend>
        <label>
          <input
            type="checkbox"
            name="amenities"
            value="pool"
          />{" "}
          Pool
        </label>
        <label>
          <input
            type="checkbox"
            name="amenities"
            value="exercise"
          />{" "}
          Exercise Room
        </label>
      </fieldset>
      <button type="submit">Search</button>
    </Form>
  );
}

当用户提交此表单时,表单将被序列化到 URL 中,类似于以下内容,具体取决于用户的选择。

/slc/hotels?sort=price&stars=4&amenities=pool&amenities=exercise

您可以从 request.url 中访问这些值。

<Route
  path="/:city/hotels"
  loader={async ({ request }) => {
    let url = new URL(request.url);
    let sort = url.searchParams.get("sort");
    let stars = url.searchParams.get("stars");
    let amenities = url.searchParams.getAll("amenities");
    return fakeGetHotels({ sort, stars, amenities });
  }}
/>

另请参阅