Skip to content

Commit 8463596

Browse files
committed
Refine form Action documentation
1 parent ebe77b0 commit 8463596

2 files changed

Lines changed: 109 additions & 129 deletions

File tree

‎src/content/reference/react-dom/components/form.md‎

Lines changed: 49 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ To create interactive controls for submitting information, render the [built-in
4242
* If you pass a function to `action`, React runs it in a [Transition](/reference/react/useTransition) following [the Action prop pattern](/reference/react/useTransition#exposing-action-props-from-components).
4343
* The function may be async. React calls it with a single argument containing the [form data](https://developer.mozilla.org/en-US/docs/Web/API/FormData) of the submitted form.
4444
* A `formAction` prop on a `<button>`, `<input type="submit">`, or `<input type="image">` overrides this `action`.
45-
* [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method): A string. Specifies the [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) (`get` or `post`). Defaults to `get`. Ignored when `action` is a function.
46-
* `onSubmit`: An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires when the form is submitted. If you also pass a function to `action`, both run unless you call `e.preventDefault()`. See [Handling form submission with an event handler](#handle-form-submission-with-an-event-handler).
45+
* [`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method): A string that specifies the HTTP method to use when `action` is a URL. Defaults to `get`.
46+
* `onSubmit`: An [`Event` handler](/reference/react-dom/components/common#event-handler) function. Fires when the form is submitted. See [Handling form submission with an event handler](#handle-form-submission-with-an-event-handler).
4747

4848
#### Caveats {/*caveats*/}
4949

@@ -58,6 +58,8 @@ To create interactive controls for submitting information, render the [built-in
5858

5959
Pass a function to the `onSubmit` event handler to run code when the form is submitted. By default, the browser sends the form data to the current URL and refreshes the page. Calling [`e.preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) in the event handler overrides this behavior.
6060

61+
If you also pass a function to `action`, React runs it after `onSubmit` unless `onSubmit` calls `e.preventDefault()`.
62+
6163
<Sandpack>
6264

6365
```js src/App.js
@@ -92,7 +94,7 @@ Reading form data with `onSubmit` works in every version of React and gives you
9294

9395
### Handling form submission with an action prop {/*handle-form-submission-with-an-action-prop*/}
9496

95-
Pass a function to the `action` prop to run it when the form is submitted. React calls the function with a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object containing the values of every input with a `name` attribute. Your inputs can be [uncontrolled](/reference/react-dom/components/input#reading-the-input-values-when-submitting-a-form)-you don't need `value`/`onChange` pairs, an `onSubmit` handler, or `e.preventDefault()`.
97+
Pass a function to the `action` prop to run it when the form is submitted. React calls the function with a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object containing the values of every input with a `name` attribute. Your inputs can be [uncontrolled](/reference/react-dom/components/input#reading-the-input-values-when-submitting-a-form). You don't need `value`/`onChange` pairs, an `onSubmit` handler, or `e.preventDefault()`.
9698

9799
When you pass a function to `action`, React:
98100

@@ -126,11 +128,11 @@ export default function Search() {
126128

127129
### Handling form submission with a Server Function {/*handle-form-submission-with-a-server-function*/}
128130

129-
Render a `<form>` with an input and submit button. Pass a Server Function (a function marked with [`'use server'`](/reference/rsc/use-server)) to the `action` prop of form to run the function when the form is submitted.
131+
Render a `<form>` with an input and submit button. Pass a Server Function (a function marked with [`'use server'`](/reference/rsc/use-server)) to the form's `action` prop to run the function when the form is submitted.
130132

131-
Passing a Server Function to `<form action>` allows users to submit forms without JavaScript enabled or before the code has loaded. This is beneficial to users who have a slow connection, device, or have JavaScript disabled and is similar to the way forms work when a URL is passed to the `action` prop.
133+
Passing a Server Function to a form's `action` prop allows users to submit the form before JavaScript loads or when JavaScript is disabled. This matches how forms behave when you pass a URL to `action`.
132134

133-
You can use hidden form fields to provide data to the `<form>`'s action. The Server Function will be called with the hidden form field data as an instance of [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData).
135+
You can use hidden form fields to pass data to the Server Function. React includes the hidden field values in the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) passed to the function.
134136

135137
```jsx
136138
import { updateCart } from './lib.js';
@@ -150,7 +152,7 @@ function AddToCart({productId}) {
150152
}
151153
```
152154

153-
In lieu of using hidden form fields to provide data to the `<form>`'s action, you can call the <CodeStep step={1}>`bind`</CodeStep> method to supply it with extra arguments. This will bind a new argument (<CodeStep step={2}>`productId`</CodeStep>) to the function in addition to the <CodeStep step={3}>`formData`</CodeStep> that is passed as an argument to the function.
155+
Instead of using a hidden form field, call the <CodeStep step={1}>`bind`</CodeStep> method to pass an extra argument to the Server Function. This binds <CodeStep step={2}>`productId`</CodeStep> as an argument before the <CodeStep step={3}>`formData`</CodeStep> that React passes to the function.
154156

155157
```jsx [[1, 8, "bind"], [2,8, "productId"], [2,4, "productId"], [3,4, "formData"]]
156158
import { updateCart } from './lib.js';
@@ -290,7 +292,7 @@ To learn more about the `useOptimistic` Hook, see the [reference documentation](
290292

291293
### Handling form submission errors {/*handling-form-submission-errors*/}
292294

293-
In some cases the function called by a `<form>`'s `action` prop throws an error. You can handle these errors by wrapping `<form>` in an Error Boundary. If the Action throws, the Error Boundary fallback will be displayed.
295+
To handle errors thrown by a function passed to a `<form>`'s `action` prop, wrap the form in an Error Boundary. React displays the boundary's fallback when the function throws.
294296

295297
<Sandpack>
296298

@@ -338,69 +340,58 @@ Displaying a form submission error message before the JavaScript bundle loads fo
338340
1. the function passed to the `<form>`'s `action` prop be a [Server Function](/reference/rsc/server-functions)
339341
1. the `useActionState` Hook be used to display the error message
340342

341-
`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.
343+
Define the Server Function in a separate file with the [`'use server'`](/reference/rsc/use-server) directive. It receives the previous state followed by the submitted `FormData`:
342344

343-
<Sandpack>
345+
```js
346+
// actions.js
347+
'use server';
344348

345-
```js src/App.js
346-
import { useActionState } from 'react';
347349
import { signUpNewUser } from './api.js';
348350

349-
export default function Page() {
350-
async function signup(prevState, formData) {
351-
'use server';
352-
const email = formData.get('email');
353-
try {
354-
await signUpNewUser(email);
355-
alert(`Added "${email}"`);
356-
} catch (err) {
357-
return err.toString();
358-
}
351+
export async function signup(previousState, formData) {
352+
const email = formData.get('email');
353+
try {
354+
await signUpNewUser(email);
355+
return null;
356+
} catch (error) {
357+
return error.message;
359358
}
360-
const [message, signupAction] = useActionState(signup, null);
361-
return (
362-
<>
363-
<h1>Signup for my newsletter</h1>
364-
<p>Signup with the same email twice to see an error</p>
365-
<form action={signupAction} id="signup-form">
366-
<label htmlFor="email">Email: </label>
367-
<input name="email" id="email" placeholder="react@example.com" />
368-
<button>Sign up</button>
369-
{!!message && <p>{message}</p>}
370-
</form>
371-
</>
372-
);
373359
}
374360
```
375361

376-
```js src/api.js hidden
377-
let emails = [];
362+
In a Client Component, pass the Server Function to `useActionState`. Pass the returned Action to the form's `action` prop and render the returned state:
378363

379-
export async function signUpNewUser(newEmail) {
380-
if (emails.includes(newEmail)) {
381-
throw new Error('This email address has already been added');
382-
}
383-
emails.push(newEmail);
364+
```js
365+
// Signup.js
366+
'use client';
367+
368+
import { useActionState } from 'react';
369+
import { signup } from './actions.js';
370+
371+
export default function Signup() {
372+
const [message, signupAction] = useActionState(signup, null);
373+
return (
374+
<form action={signupAction}>
375+
<label htmlFor="email">Email: </label>
376+
<input name="email" id="email" placeholder="react@example.com" />
377+
<button>Sign up</button>
378+
{message && <p>{message}</p>}
379+
</form>
380+
);
384381
}
385382
```
386383

387-
</Sandpack>
388-
389-
To learn more about updating state from a form Action, see the [`useActionState`](/reference/react/useActionState) docs.
384+
If the form is submitted before JavaScript loads, React includes the Server Function's returned error message in the server-rendered response.
390385

391386
---
392387

393388
### Preserving form values after submission {/*preserve-form-values-after-submission*/}
394389

395-
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.
396-
397-
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.
398-
399-
<Recipes titleText="Examples of preserving form values" titleId="examples-preserve-form-values">
390+
Submitting a form with a URL `action` clears its input state. React mirrors this behavior when `action` is a function by resetting the form's [uncontrolled fields](/reference/react-dom/components/input#reading-the-input-values-when-submitting-a-form) after the Action succeeds. When a Server Function progressively enhances a form, this keeps its behavior consistent before and after JavaScript loads. [Inputs controlled with state](/reference/react-dom/components/input#controlling-an-input-with-a-state-variable) are not cleared.
400391

401392
#### Restore fields with `useActionState` {/*with-useactionstate*/}
402393

403-
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.
394+
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`. The automatic form reset restores those default values instead of clearing the fields.
404395

405396
<Sandpack>
406397

@@ -435,95 +426,27 @@ export async function submitForm(previousState, formData) {
435426

436427
</Sandpack>
437428

438-
<Solution />
439-
440-
#### Keep every field with `onSubmit` {/*with-onsubmit-and-usetransition*/}
441-
442-
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.
443-
444-
<Sandpack>
445-
446-
```js src/App.js
447-
import { useTransition } from 'react';
448-
import { submitForm } from './api.js';
449-
450-
export default function EditForm() {
451-
const [isPending, startTransition] = useTransition();
452-
453-
function handleSubmit(e) {
454-
// Stop React from resetting the form after the Action succeeds
455-
e.preventDefault();
456-
const formData = new FormData(e.target);
457-
startTransition(async () => {
458-
await submitForm(formData);
459-
});
460-
}
461-
462-
return (
463-
<form action={submitForm} onSubmit={handleSubmit}>
464-
<input name="title" defaultValue="My draft" />
465-
<button type="submit" disabled={isPending}>
466-
{isPending ? 'Saving...' : 'Save'}
467-
</button>
468-
</form>
469-
);
470-
}
471-
```
472-
473-
```js src/api.js hidden
474-
export async function submitForm(formData) {
475-
await new Promise((res) => setTimeout(res, 1000));
476-
}
477-
```
478-
479-
</Sandpack>
480-
481-
<Solution />
482-
483-
</Recipes>
484-
485-
You can also reset only some fields, or restore values from the server on validation failure.
486-
487429
<DeepDive>
488430

489-
#### Resetting only some fields, or resetting on the server {/*resetting-only-some-fields*/}
431+
#### Choosing how to manage form values {/*choosing-how-to-manage-form-values*/}
490432

491-
The `onSubmit` approach above keeps every uncontrolled field. For finer control, you can:
433+
Choose an approach based on what should happen after submission:
492434

493-
* **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.
435+
* **Preserve selected values with `useActionState`.** The example above returns the submitted title after every submission. To preserve values only when validation fails, return the submitted `FormData` in the error state and use it to set each field's `defaultValue`. With a Server Function, React can include those values in the server response before JavaScript loads.
494436

495-
* **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:
496-
497-
```js
498-
import { useActionState } from 'react';
499-
import { submitForm } from './actions.js';
437+
* **Keep every value with `onSubmit`.** Call `e.preventDefault()`, then run the Action inside [`startTransition`](/reference/react/useTransition). Calling `preventDefault()` prevents the function passed to the form's `action` prop from running for that submission, so React does not automatically reset the form.
500438

501-
function EditForm() {
502-
// The Action returns { submitted: formData, error } on failure
503-
const [state, formAction] = useActionState(submitForm, {
504-
error: '',
505-
});
506-
return (
507-
<form action={formAction}>
508-
<input name="title" defaultValue={state.submitted?.get('title') ?? ''} />
509-
{state.error && <p>{state.error}</p>}
510-
<button type="submit">Save</button>
511-
</form>
512-
);
513-
}
514-
```
439+
* **Reset fields at a specific point.** Call the form element's [`reset()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset) method to immediately reset uncontrolled fields to their default values. To schedule the same reset inside an Action or Transition, call [`requestFormReset`](/blog/2024/12/05/react-19#form-actions) from `react-dom`.
515440

516-
Return the original `FormData` object rather than a new one so React can restore the values even before JavaScript has loaded.
441+
* **Reset the fields and component state.** Change the [`key`](/learn/preserving-and-resetting-state#resetting-a-form-with-a-key) on the component that renders the form. React recreates the component and its DOM, so its fields and local state both start over.
517442

518443
</DeepDive>
519444

520445
---
521446

522447
### Handling multiple submission types {/*handling-multiple-submission-types*/}
523448

524-
A form can have more than one submit button, each running a different Action. Set the `formAction` prop on a `<button>` to override the `<form>`'s `action` when that button submits the form.
525-
526-
When a button without `formAction` submits the form, React calls the form's `action`. When a button with `formAction` submits the form, React calls that button's `formAction` instead. For example, the form below publishes an article by default, but its **Save draft** button stores the current content without publishing it:
449+
A form can have more than one submit button, each running a different Action. A button without `formAction` runs the form's `action`; a button with `formAction` runs its own Action instead. For example, the form below publishes an article by default, but its **Save draft** button stores the current content without publishing it:
527450

528451
<Sandpack>
529452

‎src/content/reference/react/useActionState.md‎

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,14 +1184,71 @@ hr {
11841184
11851185
In this example, when the user clicks the stepper arrows, the button submits the form and `useActionState` calls `updateCartAction` with the form data. The example uses `useOptimistic` to immediately show the new quantity while the server confirms the update.
11861186
1187+
#### Displaying validation errors and preserving form values {/*displaying-validation-errors-and-preserving-form-values*/}
1188+
1189+
Return validation errors and submitted values from the Action to display an error without clearing the affected fields. Try submitting a name with fewer than three characters:
1190+
1191+
<Sandpack>
1192+
1193+
```js src/App.js active
1194+
import { useActionState } from 'react';
1195+
import { updateName } from './api.js';
1196+
1197+
const initialState = {
1198+
error: null,
1199+
submitted: null,
1200+
};
1201+
1202+
export default function UpdateName() {
1203+
const [state, submitAction, isPending] = useActionState(
1204+
updateName,
1205+
initialState
1206+
);
1207+
1208+
return (
1209+
<form action={submitAction}>
1210+
<label>
1211+
Name:{' '}
1212+
<input
1213+
name="name"
1214+
defaultValue={state.submitted?.get('name') ?? ''}
1215+
disabled={isPending}
1216+
/>
1217+
</label>
1218+
<button type="submit" disabled={isPending}>Save</button>
1219+
{state.error && <p>{state.error}</p>}
1220+
</form>
1221+
);
1222+
}
1223+
```
1224+
1225+
```js src/api.js hidden
1226+
export async function updateName(previousState, formData) {
1227+
await new Promise(resolve => setTimeout(resolve, 1000));
1228+
const name = formData.get('name');
1229+
if (typeof name !== 'string' || name.trim().length < 3) {
1230+
return {
1231+
error: 'Name must be at least three characters long',
1232+
submitted: formData,
1233+
};
1234+
}
1235+
return {
1236+
error: null,
1237+
submitted: null,
1238+
};
1239+
}
1240+
```
1241+
1242+
</Sandpack>
1243+
1244+
When validation fails, `updateName` returns an error and the submitted `FormData`. The component uses the submitted name as the input's new `defaultValue`, so React's automatic form reset preserves it. When validation succeeds, `submitted` is `null`, so the automatic reset clears the field.
1245+
11871246
<RSC>
11881247
1189-
When used with a [Server Function](/reference/rsc/server-functions), `useActionState` allows the server's response to be shown before hydration (when React attaches to server-rendered HTML) completes. You can also use the optional `permalink` parameter for progressive enhancement (allowing the form to work before JavaScript loads) on pages with dynamic content. This is typically handled by your framework for you.
1248+
When the `reducerAction` passed to `useActionState` is a [Server Function](/reference/rsc/server-functions), pass the returned `submitAction` to the form's `action` prop. React can then display the Server Function's returned value before hydration completes. Return the submitted `FormData` and use it to set each field's `defaultValue` to preserve those values before JavaScript loads. On pages with dynamic content, you can also use the optional `permalink` parameter for progressive enhancement. This is typically handled by your framework for you.
11901249
11911250
</RSC>
11921251
1193-
See the [`<form>`](/reference/react-dom/components/form#handle-form-submission-with-a-server-function) docs for more information on using Actions with forms.
1194-
11951252
---
11961253
11971254
### Handling errors {/*handling-errors*/}

0 commit comments

Comments
 (0)