diff --git a/src/content/reference/react-dom/components/form.md b/src/content/reference/react-dom/components/form.md index 10e9c67940e..ccb03f761bf 100644 --- a/src/content/reference/react-dom/components/form.md +++ b/src/content/reference/react-dom/components/form.md @@ -38,21 +38,25 @@ To create interactive controls for submitting information, render the [built-in `
` supports all [common element props.](/reference/react-dom/components/common#common-props) -[`action`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#action): a URL or function. When a URL is passed to `action` the form will behave like the HTML form component. When a function is passed to `action` the function will handle the form submission in a Transition following [the Action prop pattern](/reference/react/useTransition#exposing-action-props-from-components). The function passed to `action` may be async and will be called with a single argument containing the [form data](https://developer.mozilla.org/en-US/docs/Web/API/FormData) of the submitted form. The `action` prop can be overridden by a `formAction` attribute on a ` + +
- ); } ``` @@ -143,8 +157,8 @@ import { updateCart } from './lib.js'; function AddToCart({productId}) { async function addToCart(productId, formData) { - "use server"; - await updateCart(productId) + 'use server'; + await updateCart(productId); } const addProductToCart = addToCart.bind(null, productId); return ( @@ -155,9 +169,10 @@ function AddToCart({productId}) { } ``` -When `
` is rendered by a [Server Component](/reference/rsc/use-client), and a [Server Function](/reference/rsc/server-functions) is passed to the ``'s `action` prop, the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). +--- + +### Displaying a pending state during form submission {/*display-a-pending-state-during-form-submission*/} -### Display a pending state during form submission {/*display-a-pending-state-during-form-submission*/} To display a pending state when a form is being submitted, you can call the `useFormStatus` Hook in a component rendered in a `` and read the `pending` property returned. Here, we use the `pending` property to indicate the form is submitting. @@ -165,14 +180,14 @@ Here, we use the `pending` property to indicate the form is submitting. ```js src/App.js -import { useFormStatus } from "react-dom"; -import { submitForm } from "./actions.js"; +import { useFormStatus } from 'react-dom'; +import { submitForm } from './actions.js'; function Submit() { const { pending } = useFormStatus(); return ( ); } @@ -191,31 +206,33 @@ export default function App() { ``` ```js src/actions.js hidden -export async function submitForm(query) { - await new Promise((res) => setTimeout(res, 1000)); +export async function submitForm(formData) { + await new Promise((res) => setTimeout(res, 1000)); } ``` -To learn more about the `useFormStatus` Hook see the [reference documentation](/reference/react-dom/hooks/useFormStatus). +To learn more about the `useFormStatus` Hook, see the [reference documentation](/reference/react-dom/hooks/useFormStatus). + +--- ### Optimistically updating form data {/*optimistically-updating-form-data*/} + The `useOptimistic` Hook provides a way to optimistically update the user interface before a background operation, like a network request, completes. In the context of forms, this technique helps to make apps feel more responsive. When a user submits a form, instead of waiting for the server's response to reflect the changes, the interface is immediately updated with the expected outcome. For example, when a user types a message into the form and hits the "Send" button, the `useOptimistic` Hook allows the message to immediately appear in the list with a "Sending..." label, even before the message is actually sent to a server. This "optimistic" approach gives the impression of speed and responsiveness. The form then attempts to truly send the message in the background. Once the server confirms the message has been received, the "Sending..." label is removed. - ```js src/App.js -import { useOptimistic, useState, useRef } from "react"; -import { deliverMessage } from "./actions.js"; +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")); + addOptimisticMessage(formData.get('message')); formRef.current.reset(); await sendMessage(formData); } @@ -248,17 +265,17 @@ function Thread({ messages, sendMessage }) { export default function App() { const [messages, setMessages] = useState([ - { text: "Hello there!", sending: false, key: 1 } + { text: 'Hello there!', sending: false, key: 1 } ]); async function sendMessage(formData) { - const sentMessage = await deliverMessage(formData.get("message")); + const sentMessage = await deliverMessage(formData.get('message')); setMessages((messages) => [...messages, { text: sentMessage }]); } return ; } ``` -```js src/actions.js +```js src/actions.js hidden export async function deliverMessage(message) { await new Promise((res) => setTimeout(res, 1000)); return message; @@ -267,21 +284,22 @@ export async function deliverMessage(message) { -[//]: # 'Uncomment the next line, and delete this line after the `useOptimistic` reference documentation page is published' -[//]: # 'To learn more about the `useOptimistic` Hook see the [reference documentation](/reference/react/useOptimistic).' +To learn more about the `useOptimistic` Hook, see the [reference documentation](/reference/react/useOptimistic). + +--- ### Handling form submission errors {/*handling-form-submission-errors*/} -In some cases the function called by a ``'s `action` prop throws an error. You can handle these errors by wrapping `` in an Error Boundary. If the function called by a ``'s `action` prop throws an error, the fallback for the error boundary will be displayed. +In some cases the function called by a ``'s `action` prop throws an error. You can handle these errors by wrapping `` in an Error Boundary. If the Action throws, the Error Boundary fallback will be displayed. ```js src/App.js -import { ErrorBoundary } from "react-error-boundary"; +import { ErrorBoundary } from 'react-error-boundary'; export default function Search() { function search() { - throw new Error("search error"); + throw new Error('search error'); } return ( ); } - ``` ```json package.json hidden @@ -305,14 +322,15 @@ export default function Search() { "react-scripts": "^5.0.0", "react-error-boundary": "4.0.3" }, - "main": "/index.js", - "devDependencies": {} + "main": "/index.js" } ``` -### Display a form submission error without JavaScript {/*display-a-form-submission-error-without-javascript*/} +--- + +### Displaying a form submission error without JavaScript {/*display-a-form-submission-error-without-javascript*/} Displaying a form submission error message before the JavaScript bundle loads for progressive enhancement requires that: @@ -320,18 +338,18 @@ Displaying a form submission error message before the JavaScript bundle loads fo 1. the function passed to the ``'s `action` prop be a [Server Function](/reference/rsc/server-functions) 1. the `useActionState` Hook be used to display the error message -`useActionState` takes two parameters: a [Server Function](/reference/rsc/server-functions) and an initial state. `useActionState` returns two values, a state variable and an action. The action returned by `useActionState` should be passed to the `action` prop of the form. The state variable returned by `useActionState` can be used to display an error message. The value returned by the Server Function passed to `useActionState` will be used to update the state variable. +`useActionState` takes two parameters: a [Server Function](/reference/rsc/server-functions) and an initial state. `useActionState` returns two values, a state variable and an Action. The Action returned by `useActionState` should be passed to the `action` prop of the form. The state variable returned by `useActionState` can be used to display an error message. The value returned by the Server Function passed to `useActionState` will be used to update the state variable. ```js src/App.js -import { useActionState } from "react"; -import { signUpNewUser } from "./api"; +import { useActionState } from 'react'; +import { signUpNewUser } from './api.js'; export default function Page() { async function signup(prevState, formData) { - "use server"; - const email = formData.get("email"); + 'use server'; + const email = formData.get('email'); try { await signUpNewUser(email); alert(`Added "${email}"`); @@ -360,7 +378,7 @@ let emails = []; export async function signUpNewUser(newEmail) { if (emails.includes(newEmail)) { - throw new Error("This email address has already been added"); + throw new Error('This email address has already been added'); } emails.push(newEmail); } @@ -368,32 +386,190 @@ export async function signUpNewUser(newEmail) { -Learn more about updating state from a form action with the [`useActionState`](/reference/react/useActionState) docs +To learn more about updating state from a form Action, see the [`useActionState`](/reference/react/useActionState) docs. + +--- + +### Preserving form values after submission {/*preserve-form-values-after-submission*/} + +By default, the browser clears a form's input state after submission. Forms with a URL `action` follow this behavior, and React mirrors it when `action` is a function so the form behaves consistently before and after JavaScript loads. + +When you pass a function to `action` or `formAction`, React resets the form's [uncontrolled fields](/reference/react-dom/components/input#reading-the-input-values-when-submitting-a-form) after the Action succeeds. This reset only affects uncontrolled fields-[inputs controlled with state](/reference/react-dom/components/input#controlling-an-input-with-a-state-variable) are not cleared. + + + +#### Restore fields with `useActionState` {/*with-useactionstate*/} + +Pass the action returned by [`useActionState`](/reference/react/useActionState) to the `action` prop. Return the values you want to keep from your Action, and pass them to each field's `defaultValue`. React restores those values instead of clearing them. + + + +```js src/App.js +import { useActionState } from 'react'; +import { submitForm } from './api.js'; + +export default function EditForm() { + const [state, dispatchAction, isPending] = useActionState(submitForm, { + title: 'My draft', + }); + + return ( + + + + + ); +} +``` + +```js src/api.js hidden +export async function submitForm(previousState, formData) { + await new Promise((res) => setTimeout(res, 1000)); + return { + title: formData.get('title'), + }; +} +``` + + + + + +#### Keep every field with `onSubmit` {/*with-onsubmit-and-usetransition*/} + +Call `e.preventDefault()` in an `onSubmit` handler and run the Action yourself with [`startTransition`](/reference/react/useTransition). React doesn't reset the form because the `action` prop never runs. Keep passing `action` so the form still submits before JavaScript loads. + + + +```js src/App.js +import { useTransition } from 'react'; +import { submitForm } from './api.js'; + +export default function EditForm() { + const [isPending, startTransition] = useTransition(); + + function handleSubmit(e) { + // Stop React from resetting the form after the Action succeeds + e.preventDefault(); + const formData = new FormData(e.target); + startTransition(async () => { + await submitForm(formData); + }); + } + + return ( +
+ + +
+ ); +} +``` + +```js src/api.js hidden +export async function submitForm(formData) { + await new Promise((res) => setTimeout(res, 1000)); +} +``` + +
+ + + +
+ +You can also reset only some fields, or restore values from the server on validation failure. + + + +#### Resetting only some fields, or resetting on the server {/*resetting-only-some-fields*/} + +The `onSubmit` approach above keeps every uncontrolled field. For finer control, you can: + +* **Reset from your own Action API.** If you build an Action-based API and still want the form to reset after the Action runs, call [`requestFormReset`](/blog/2024/12/05/react-19#form-actions) from `react-dom` with the form element inside the Transition. + +* **Reset to server-provided values on validation failure.** The [`useActionState`](#with-useactionstate) example above preserves values after a successful submission. When an Action validates input on the server, you can return the submitted `FormData` and pass it to each field's `defaultValue`. React restores those values instead of clearing them, and the form keeps working before JavaScript loads: + +```js +import { useActionState } from 'react'; +import { submitForm } from './actions.js'; + +function EditForm() { + // The Action returns { submitted: formData, error } on failure + const [state, formAction] = useActionState(submitForm, { + error: '', + }); + return ( +
+ + {state.error &&

{state.error}

} + +
+ ); +} +``` + +Return the original `FormData` object rather than a new one so React can restore the values even before JavaScript has loaded. + +
+ +--- ### Handling multiple submission types {/*handling-multiple-submission-types*/} -Forms can be designed to handle multiple submission actions based on the button pressed by the user. Each button inside a form can be associated with a distinct action or behavior by setting the `formAction` prop. +A form can have more than one submit button, each running a different Action. Set the `formAction` prop on a `