內建瀏覽器 <form>
元件 可讓您建立用於提交資訊的互動式控件。
<form action={search}>
<input name="query" />
<button type="submit">Search</button>
</form>
參考
<form>
要建立用於提交資訊的互動式控件,請渲染內建瀏覽器 <form>
元件。
<form action={search}>
<input name="query" />
<button type="submit">Search</button>
</form>
屬性
<form>
支援所有 常用元件屬性。
action
:URL 或函式。當 URL 傳遞給 action
時,表單的行為將與 HTML 表單元件相同。當函式傳遞給 action
時,該函式將處理表單提交。傳遞給 action
的函式可以是非同步的,並且將使用包含已提交表單的 表單數據 的單個參數調用。 action
屬性可以被 <button>
、<input type="submit">
或 <input type="image">
元件上的 formAction
屬性覆蓋。
注意事項
- 當函式傳遞給
action
或formAction
時,無論method
屬性的值為何,HTTP 方法都將是 POST。
用法
export default function Search() { function search(formData) { const query = formData.get("query"); alert(`You searched for '${query}'`); } return ( <form action={search}> <input name="query" /> <button type="submit">Search</button> </form> ); }
使用伺服器函式處理表單提交
渲染一個包含輸入框和提交按鈕的 <form>
。將伺服器函式(標記為 'use server'
的函式)傳遞給表單的 action
屬性,以便在表單提交時執行該函式。
將伺服器函式傳遞給 <form action>
允許用戶在未啟用 JavaScript 或程式碼載入之前提交表單。這對於網路連線速度慢、設備效能低或已停用 JavaScript 的用戶來說非常有利,並且類似於將 URL 傳遞給 action
屬性時表單的工作方式。
您可以使用隱藏的表單欄位將資料提供給 <form>
的 action。伺服器函式將使用隱藏的表單欄位資料作為 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">Add to Cart</button>
</form>
);
}
除了使用隱藏的表單欄位將資料提供給 <form>
的 action 之外,您還可以呼叫 bind
方法來提供額外的參數。這除了將 formData
作為參數傳遞給函式之外,還會將新的參數(productId
)綁定到函式。
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">Add to Cart</button>
</form>
);
}
當 <form>
由 伺服器元件 渲染,並且將 伺服器函式 傳遞給 <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 ? "Submitting..." : "Submit"} </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> (Sending...)</small>} </div> ))} <form action={formAction} ref={formRef}> <input type="text" name="message" placeholder="Hello!" /> <button type="submit">Send</button> </form> </> ); } export default function App() { const [messages, setMessages] = useState([ { text: "Hello there!", sending: false, key: 1 } ]); async function sendMessage(formData) { const sentMessage = await deliverMessage(formData.get("message")); setMessages([...messages, { text: sentMessage }]); } return <Thread messages={messages} sendMessage={sendMessage} />; }
處理表單提交錯誤
在某些情況下,`<form>
` 的 `action` 屬性所呼叫的函式會拋出錯誤。您可以透過將 `<form>
` 包裹在錯誤邊界 (Error Boundary) 中來處理這些錯誤。如果 `<form>
` 的 `action` 屬性所呼叫的函式拋出錯誤,則會顯示錯誤邊界的後備 (fallback) 內容。
import { ErrorBoundary } from "react-error-boundary"; export default function Search() { function search() { throw new Error("search error"); } return ( <ErrorBoundary fallback={<p>There was an error while submitting the form</p>} > <form action={search}> <input name="query" /> <button type="submit">Search</button> </form> </ErrorBoundary> ); }
在沒有 JavaScript 的情況下顯示表單提交錯誤
為了漸進式增強,在 JavaScript 程式碼載入之前顯示表單提交錯誤訊息,需要滿足以下條件:
- `
<form>
` 必須由伺服器元件 (Server Component)渲染 - 傳遞給 `
<form>
` 的 `action` 屬性的函式必須是伺服器函式 (Server Function) - 使用 `useActionState` Hook 來顯示錯誤訊息
`useActionState` 接受兩個參數:一個伺服器函式 (Server Function)和一個初始狀態。`useActionState` 返回兩個值,一個狀態變數和一個動作 (action)。`useActionState` 返回的動作應傳遞給表單的 `action` 屬性。`useActionState` 返回的狀態變數可以用於顯示錯誤訊息。傳遞給 `useActionState` 的伺服器函式返回的值將用於更新狀態變數。
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(`Added "${email}"`); } catch (err) { return err.toString(); } } const [message, signupAction] = useActionState(signup, null); return ( <> <h1>Signup for my newsletter</h1> <p>Signup with the same email twice to see an error</p> <form action={signupAction} id="signup-form"> <label htmlFor="email">Email: </label> <input name="email" id="email" placeholder="react@example.com" /> <button>Sign up</button> {!!message && <p>{message}</p>} </form> </> ); }
透過useActionState
文件了解更多關於從表單動作更新狀態的資訊
處理多種提交類型
可以設計表單,以便根據使用者按下的按鈕來處理多個提交動作。透過設定 `formAction` 屬性,表單內的每個按鈕都可以與不同的動作或行為關聯。
當使用者按下特定按鈕時,表單會被提交,並執行由該按鈕的屬性和動作定義的相應動作。例如,一個表單預設情況下可能會提交一篇文章以供審閱,但有一個單獨的按鈕,其 `formAction` 設定為將文章儲存為草稿。
export default function Search() { function publish(formData) { const content = formData.get("content"); const button = formData.get("button"); alert(`'${content}' was published with the '${button}' button`); } function save(formData) { const content = formData.get("content"); alert(`Your draft of '${content}' has been saved!`); } return ( <form action={publish}> <textarea name="content" rows={4} cols={40} /> <br /> <button type="submit" name="button" value="submit">Publish</button> <button formAction={save}>Save draft</button> </form> ); }