main
分支
main (6.23.1)dev
版本
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

表單將會提交到的網址,像 HTML 表單動作。唯一的不同是預設的動作。對於 HTML 表單,其預設值是完整的網址。對於 <Form>,其預設值是在內容中最近的路由的相對網址。

考量以下的路由和元件

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>;

如果目前的網址是 "/projects/123",那麼在子路由,`ProjectsPage` 內部的表單將會有像你預期的預設動作:"/projects/123"。在這種情況下,其中路由是最深層的相符路由,所以 <Form> 和純 HTML 表單都產生相同的結果。

但是,ProjectsLayout 內部的表單會指向 "/projects",而不是完整的網址。換句話說,它指向表單被渲染的路由中,網址的相符片段。

這有助於在路由模組周圍增加一些慣例時提高可攜性以及表單和其動作處理常式共置。

如果你需要向不同的路由張貼,那麼新增一個 action prop

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

請參閱

請參閱 useResolvedPath 文件中的Splat 路徑段落,以獲取 future.v7_relativeSplatPath 未來標記的注意事項,以得知在 splat 路由中 useNavigate() 行為的相對關係

method

這會決定要使用的 HTTP 動詞。和純 HTML 表單方法 相同,除了它也會支援「put」、「patch」,和「delete」,以及「get」和「post」。預設值是「get」。

GET 提交

預設方法是「get」。GET 提交不會呼叫動作。GET 提交就像平常的導航(使用者點選連結),但使用者可以提供從表單傳送到網址的搜尋參數。

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

假設使用者輸入「慢跑鞋」並提交表單。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);
  }}
/>

變異提交

所有其他方法都是「突變提交」,表示你打算透過 POST、PUT、PATCH 或 DELETE 變更資料。請注意,一般 HTML 表單僅支援「post」和「get」,我們也傾向只使用這兩個方法。

當使用者提交表單時,React Router 會將 `action` 比對至應用程式的路由,並使用序列化後的 FormData 呼叫 `<Route action>`。當動作完成時,頁面上的所有 loader 資料都會自動重新驗證,以維持你的使用者介面與資料同步。

方法可在所呼叫的路由動作中的 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` 會重新驗證,使用者介面會自動與新資料同步。

你可以透過指定 `<Form navigate={false}>` 來告訴表單略過導航並在內部使用 fetcher。這基本上是 useFetcher() + <fetcher.Form> 的簡寫,你不需要在意產生的資料,只需要啟動提交並透過程 useFetchers() 存取等待中的狀態。

fetcherKey

在使用非導航 `Form` 時,也可以選擇透過 `<Form navigate={false} fetcherKey="my-key">` 指定要使用的 fetcher 金鑰。

replace

指示表單取代歷史記錄堆疊中的目前記錄項,而不是推進新記錄項。

<Form replace />

預設行為會根據表單行為而定

  • method=get 表單預設為 false
  • 提交方法會根據 `formAction` 和 `action` 行為而定
    • 如果你的 `action` 擲回例外,預設為 false
    • 如果你的 `action` 重新導向至目前位置,預設為 true
    • 如果你的 `action` 重新導向至其他位置,預設為 false
    • 如果你的 `formAction` 是目前位置,預設為 true
    • 否則預設為 false

我們發現使用 get 時,使用者通常希望可以按一下「返回」來查看之前的搜尋結果/過濾條件等。但是使用其他方法預設為 true,以避免提示「您確定要重新提交表單嗎?」。請注意,即使 replace={false},當按下「返回」按鈕且方法為 post、put、patch 或 delete 時,React Router 不會 重新提交表單。

換句話說,這僅對 GET 提交真的有用,而且您希望避免「返回」按鈕顯示之前的結果。

relative

預設路徑相對應於路由階層,因此 .. 將向上移動一級 Route。偶爾,您可能會發現有匹配但沒有意義的嵌套網址樣式,而且偏好使用相對 路徑 路由。您可以透過 <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 });
  }}
/>

請參閱