Skip to content

Commit 7dd9e90

Browse files
committed
Clarify form value preservation examples and guidance
1 parent b7e305a commit 7dd9e90

2 files changed

Lines changed: 95 additions & 71 deletions

File tree

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

Lines changed: 94 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,9 @@ function AddToCart({ productId }) {
213213
}
214214
```
215215

216-
`addToCart.bind(null, productId)` calls `addToCart(productId, formData)`.
216+
When the form is submitted, React calls `addProductToCart(formData)`, which calls `addToCart(productId, formData)`.
217217

218-
[`useActionState`](/reference/react/useActionState) passes the previous state before `FormData`. Bound arguments come first, so the Server Function receives the bound values, the previous state, and then `formData`. Define that function in a separate file and bind it from the Client Component.
218+
If you also use [`useActionState`](/reference/react/useActionState), the Server Function receives `productId`, `previousState`, and `formData`, in that order. Define that function in a separate file and bind it from the Client Component.
219219

220220
#### A function in the component {/*passing-extra-data-function*/}
221221

@@ -248,15 +248,15 @@ export async function updateCart(productId, formData) {
248248
}
249249
```
250250

251-
Users can submit this form only after JavaScript loads.
251+
Because `addToCart` runs in the browser, the component's JavaScript must load before the form can submit.
252252

253253
</DeepDive>
254254

255255
---
256256

257257
### Displaying a pending state during form submission {/*display-a-pending-state-during-form-submission*/}
258258

259-
To display a pending state when a form is being submitted, you can call the `useFormStatus` Hook in a component rendered in a `<form>` and read the `pending` property returned.
259+
To display a pending state when a form is being submitted, you can call the [`useFormStatus`](/reference/react-dom/hooks/useFormStatus) Hook in a component rendered in a `<form>` and read the `pending` property returned.
260260

261261
Here, we use the `pending` property to indicate the form is submitting.
262262

@@ -302,14 +302,14 @@ To learn more about the `useFormStatus` Hook, see the [reference documentation](
302302

303303
### Optimistically updating form data {/*optimistically-updating-form-data*/}
304304

305-
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.
305+
The [`useOptimistic`](/reference/react/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.
306306

307307
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.
308308

309309
<Sandpack>
310310

311311
```js src/App.js
312-
import { useOptimistic, useState, useRef } from 'react';
312+
import { startTransition, useOptimistic, useState, useRef } from 'react';
313313
import { deliverMessage } from './actions.js';
314314

315315
function Thread({ messages, sendMessage }) {
@@ -352,7 +352,9 @@ export default function App() {
352352
]);
353353
async function sendMessage(formData) {
354354
const sentMessage = await deliverMessage(formData.get('message'));
355-
setMessages((messages) => [...messages, { text: sentMessage }]);
355+
startTransition(() => {
356+
setMessages((messages) => [...messages, { text: sentMessage }]);
357+
});
356358
}
357359
return <Thread messages={messages} sendMessage={sendMessage} />;
358360
}
@@ -374,7 +376,7 @@ To learn more about the `useOptimistic` Hook, see the [reference documentation](
374376

375377
### Handling form submission errors {/*handling-form-submission-errors*/}
376378

377-
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.
379+
To handle errors thrown by a function passed to a `<form>`'s `action` prop, wrap the form in an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). React displays the boundary's fallback when the function throws.
378380

379381
<Sandpack>
380382

@@ -412,15 +414,13 @@ export default function Search() {
412414

413415
</Sandpack>
414416

415-
---
416-
417-
### Displaying a form submission error without JavaScript {/*display-a-form-submission-error-without-javascript*/}
417+
#### Handling form submission errors before JavaScript loads {/*display-a-form-submission-error-without-javascript*/}
418418

419419
Displaying a form submission error message before the JavaScript bundle loads for progressive enhancement requires that:
420420

421421
1. `<form>` be rendered by a [Client Component](/reference/rsc/use-client)
422422
1. the function passed to the `<form>`'s `action` prop be a [Server Function](/reference/rsc/server-functions)
423-
1. the `useActionState` Hook be used to display the error message
423+
1. the [`useActionState`](/reference/react/useActionState) Hook be used to display the error message
424424

425425
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`:
426426

@@ -469,75 +469,56 @@ If the form is submitted before JavaScript loads, React includes the Server Func
469469

470470
### Preserving form values after an Action {/*preserve-form-values-after-an-action*/}
471471

472-
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. To keep the submitted values, store each value in state and pass it to that field's `defaultValue`. The reset then restores each field to its `defaultValue` instead of clearing it.
473-
474-
To keep those values when users submit the form before JavaScript loads, render the `<form>` from a [Client Component](/reference/rsc/use-client) and pass a [Server Function](/reference/rsc/server-functions) to [`useActionState`](/reference/react/useActionState). Pass the Action it returns to the form's `action` prop. The Server Function receives the previous state, followed by the submitted `FormData`, as shown in [Displaying a form submission error without JavaScript](#display-a-form-submission-error-without-javascript). Return an object with the values to keep, and pass each value, such as `state.title`, to the matching field's `defaultValue`:
475-
476-
```js
477-
// actions.js
478-
'use server';
479-
480-
import { saveTitle } from './api.js';
481-
482-
export async function updateTitle(previousState, formData) {
483-
const title = formData.get('title');
484-
await saveTitle(title);
485-
return { title };
486-
}
487-
```
472+
When a function passed to `action` or `formAction` completes without throwing, React resets the form's [uncontrolled fields](/reference/react-dom/components/input#reading-the-input-values-when-submitting-a-form). You may want to keep the submitted values after saving edits, or when the Action returns a [validation error](/reference/react/useActionState#displaying-validation-errors-and-preserving-form-values).
488473

489-
```js
490-
// EditForm.js
491-
'use client';
474+
To preserve those values, you can store them with [`useState`](/reference/react/useState) and pass them to each field's [`defaultValue`](/reference/react-dom/components/input#providing-an-initial-value-for-an-input). Update the state when the Action finishes. React applies the updated defaults before resetting the form, so the fields keep those values.
492475

493-
import { useActionState } from 'react';
494-
import { updateTitle } from './actions.js';
495-
496-
export default function EditForm() {
497-
const [state, updateTitleAction, isPending] = useActionState(updateTitle, {
498-
title: 'My draft',
499-
});
500-
501-
return (
502-
<form action={updateTitleAction}>
503-
<input name="title" defaultValue={state.title} />
504-
<button type="submit" disabled={isPending}>
505-
{isPending ? 'Saving...' : 'Save'}
506-
</button>
507-
</form>
508-
);
509-
}
510-
```
511-
512-
Users can submit the form before JavaScript loads. After the Action succeeds, `defaultValue={state.title}` restores the title from the returned object.
513-
514-
For a form that submits only after JavaScript loads, store the values with [`useState`](/reference/react/useState). Update that state when the Action finishes. React applies the new `defaultValue`, then resets the form, so each field shows the submitted value:
476+
Try changing the title and clicking **Save**. In this example, [`useFormStatus`](/reference/react-dom/hooks/useFormStatus) shows "Saving..." during a simulated one-second request. Then `setTitle` updates the saved title and preserves it in the input.
515477

516478
<Sandpack>
517479

518480
```js src/App.js
519-
import { useState } from 'react';
481+
import { startTransition, useState } from 'react';
482+
import SaveButton from './SaveButton.js';
520483

521484
export default function EditForm() {
522485
const [title, setTitle] = useState('My draft');
523486

524487
async function saveAction(formData) {
525488
await new Promise((res) => setTimeout(res, 1000));
526-
setTitle(formData.get('title'));
489+
startTransition(() => {
490+
setTitle(formData.get('title'));
491+
});
527492
}
528493

529494
return (
530495
<form action={saveAction}>
531496
<input name="title" defaultValue={title} />
532-
<button type="submit">Save</button>
497+
<SaveButton />
498+
<p>Saved title: {title}</p>
533499
</form>
534500
);
535501
}
536502
```
537503

504+
```js src/SaveButton.js hidden
505+
import { useFormStatus } from 'react-dom';
506+
507+
export default function SaveButton() {
508+
const { pending } = useFormStatus();
509+
return (
510+
<button type="submit" disabled={pending}>
511+
{pending ? 'Saving...' : 'Save'}
512+
</button>
513+
);
514+
}
515+
```
516+
538517
</Sandpack>
539518

540-
Call a Server Function from `saveAction`, then store its return value in state. Because `saveAction` is defined in the Client Component, users can submit this form only after JavaScript loads:
519+
The call to `setTitle` [after `await`](/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition) needs another [`startTransition`](/reference/react/startTransition) to mark it as a Transition update.
520+
521+
`saveAction` can also call a [Server Function](/reference/rsc/server-functions) and store its return value:
541522

542523
```js
543524
// actions.js
@@ -552,25 +533,58 @@ export async function submitTitle(formData) {
552533
}
553534
```
554535

536+
Add [`'use client'`](/reference/rsc/use-client) at the top of the component's file, import `submitTitle`, and replace `saveAction`. The rest of the form stays the same:
537+
538+
```js
539+
import { submitTitle } from './actions.js';
540+
541+
async function saveAction(formData) {
542+
const savedTitle = await submitTitle(formData);
543+
startTransition(() => {
544+
setTitle(savedTitle);
545+
});
546+
}
547+
```
548+
549+
#### Preserving form values before JavaScript loads {/*preserving-form-values-before-javascript-loads*/}
550+
551+
With [`useActionState`](/reference/react/useActionState), React can display a [Server Function](/reference/rsc/server-functions)'s returned value before hydration completes. The form can use those values as its new defaults. In a [Client Component](/reference/rsc/use-client), `useActionState` receives the Server Function and returns an Action for the form's `action` prop.
552+
553+
See [Handling form submission errors before JavaScript loads](#display-a-form-submission-error-without-javascript) for the same behavior with errors.
554+
555+
The Server Function receives the previous state followed by the submitted `FormData`. In this example, `updateTitle` saves the title and returns `{ title }`, which becomes the new state. Passing `state.title` to `defaultValue` preserves the saved title after the reset:
556+
557+
```js
558+
// actions.js
559+
'use server';
560+
561+
import { saveTitle } from './api.js';
562+
563+
export async function updateTitle(previousState, formData) {
564+
const title = formData.get('title');
565+
await saveTitle(title);
566+
return { title };
567+
}
568+
```
569+
555570
```js
556571
// EditForm.js
557572
'use client';
558573

559-
import { useState } from 'react';
560-
import { submitTitle } from './actions.js';
574+
import { useActionState } from 'react';
575+
import { updateTitle } from './actions.js';
561576

562577
export default function EditForm() {
563-
const [title, setTitle] = useState('My draft');
564-
565-
async function saveAction(formData) {
566-
const savedTitle = await submitTitle(formData);
567-
setTitle(savedTitle);
568-
}
578+
const [state, updateTitleAction, isPending] = useActionState(updateTitle, {
579+
title: 'My draft',
580+
});
569581

570582
return (
571-
<form action={saveAction}>
572-
<input name="title" defaultValue={title} />
573-
<button type="submit">Save</button>
583+
<form action={updateTitleAction}>
584+
<input name="title" defaultValue={state.title} />
585+
<button type="submit" disabled={isPending}>
586+
{isPending ? 'Saving...' : 'Save'}
587+
</button>
574588
</form>
575589
);
576590
}
@@ -584,7 +598,7 @@ The examples above keep every submitted value by storing it before the reset. Yo
584598

585599
* **Keep values only when validation fails.** Return the same state shape from every submission. When validation fails, include the submitted `FormData` and read each field with `state.submitted?.get('title') ?? ''`. When validation succeeds, set that `FormData` to `null` so the reset clears those fields. [`useActionState`](/reference/react/useActionState#displaying-validation-errors-and-preserving-form-values) shows this pattern.
586600

587-
* **Skip the reset with `onSubmit`.** Handle the submission in `onSubmit`, call `e.preventDefault()`, and run the Action inside [`startTransition`](/reference/react/useTransition). Because the form's `action` does not run, React does not reset the form. Users can submit this form only after JavaScript loads.
601+
* **Skip the reset with `onSubmit`.** Handle the submission in `onSubmit`, call `e.preventDefault()`, and run the Action inside [`startTransition`](/reference/react/useTransition). Because the form's `action` does not run, React does not reset the form.
588602

589603
* **Reset at a specific moment.** 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 a reset as part of an Action or Transition, call [`requestFormReset`](/blog/2024/12/05/react-19#form-actions) from `react-dom` instead.
590604

@@ -596,7 +610,7 @@ The examples above keep every submitted value by storing it before the reset. Yo
596610

597611
### Handling multiple submission types {/*handling-multiple-submission-types*/}
598612

599-
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:
613+
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. It uses [`useActionState`](/reference/react/useActionState) to preserve the saved draft:
600614

601615
<Sandpack>
602616

@@ -650,3 +664,13 @@ export default function ArticleForm() {
650664
```
651665
652666
</Sandpack>
667+
668+
---
669+
670+
## Troubleshooting {/*troubleshooting*/}
671+
672+
### My form fields are cleared after submission {/*my-form-fields-are-cleared-after-submission*/}
673+
674+
When a function passed to `action` or `formAction` succeeds, React resets the form's uncontrolled fields to their default values. Text fields without a `defaultValue` become empty. Returning a validation error instead of throwing also triggers this reset.
675+
676+
To keep a submitted value, store it in state and pass it to the field's `defaultValue`. See [Preserving form values after an Action](#preserve-form-values-after-an-action) for examples, or [preserving values when validation fails](/reference/react/useActionState#displaying-validation-errors-and-preserving-form-values).

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,7 @@ When validation fails, `updateName` returns an error and the submitted `FormData
12451245
12461246
<RSC>
12471247
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.
1248+
The example above runs `updateName` in the browser. To display the error and preserve the submitted name before JavaScript loads, make `updateName` a [Server Function](/reference/rsc/server-functions). Keep passing `submitAction` to the form's `action` prop. React can then display the Server Function's returned value before hydration completes. See [preserving form values before JavaScript loads](/reference/react-dom/components/form#preserving-form-values-before-javascript-loads) for the setup. On pages with dynamic content, you can also use the optional [`permalink`](#parameters) parameter for progressive enhancement.
12491249
12501250
</RSC>
12511251

0 commit comments

Comments
 (0)