From 79c1cf828e825ad4fe03ae7e35c1e3c02f8aef03 Mon Sep 17 00:00:00 2001 From: bill Date: Tue, 20 Oct 2026 20:31:04 +1100 Subject: [PATCH] doc content improvement --- src/components/TabGroup.module.css | 10 +- src/content/docs/createFormControl.mdx | 22 +++- src/content/docs/usecontroller.mdx | 118 +++++++++++++----- src/content/docs/usecontroller/controller.mdx | 91 ++++++++------ src/content/docs/usefieldarray.mdx | 90 ++++++++++++- src/content/docs/useform.mdx | 31 +++++ src/content/docs/useform/formstate.mdx | 4 +- src/content/docs/useform/getvalues.mdx | 2 + src/content/docs/useform/handlesubmit.mdx | 30 +++++ src/content/docs/useform/register.mdx | 42 +++++++ src/content/docs/useform/reset.mdx | 1 + src/content/docs/useform/setvalue.mdx | 19 ++- src/content/docs/useform/subscribe.mdx | 72 ++++++++--- src/content/docs/useform/watch.mdx | 3 +- src/content/docs/useformcontext.mdx | 8 ++ src/content/docs/useformstate.mdx | 45 +++++++ src/content/docs/usewatch.mdx | 1 + src/content/faqs.mdx | 19 ++- 18 files changed, 500 insertions(+), 108 deletions(-) diff --git a/src/components/TabGroup.module.css b/src/components/TabGroup.module.css index 6b4ead343..04168cd54 100644 --- a/src/components/TabGroup.module.css +++ b/src/components/TabGroup.module.css @@ -1,6 +1,7 @@ .buttonTabGroup { - display: grid; - grid-auto-flow: column; + display: flex; + flex-wrap: wrap; + gap: 3px; } .buttonTabGroup > button { @@ -12,10 +13,7 @@ transition: var(--transition); text-transform: uppercase; letter-spacing: 0.05em; -} - -.buttonTabGroup > button:nth-child(n + 2) { - margin-left: 3px; + flex: 1 1 auto; } .buttonTabGroup > button:hover { diff --git a/src/content/docs/createFormControl.mdx b/src/content/docs/createFormControl.mdx index 6a12df99b..759595738 100644 --- a/src/content/docs/createFormControl.mdx +++ b/src/content/docs/createFormControl.mdx @@ -35,14 +35,24 @@ This function creates the entire form state subscription and allows you to subsc - - You should either use this API or the Context API - ```tsx - const props = createFormControl() - // ❌ You don't need a provider +- You should either use this API or the Context API + + ```tsx + const props = createFormControl() + + // ❌ You don't need a provider + + // ✅ Use method directly from createFormControl + ``` + +- Create the control once. Passing a new instance to `useForm({ formControl })` re-initializes the form. A control created at module scope is a single form shared by every component that uses it and, during server rendering, by every request. For one form per component instance, create it lazily: + + ```tsx + const [form] = useState(() => createFormControl({ defaultValues })) + const { register } = useForm({ formControl: form.formControl }) + ``` - // ✅ Use method directly from createFormControl - ``` ##### Examples: diff --git a/src/content/docs/usecontroller.mdx b/src/content/docs/usecontroller.mdx index a1ea883ec..62d39389b 100644 --- a/src/content/docs/usecontroller.mdx +++ b/src/content/docs/usecontroller.mdx @@ -40,40 +40,40 @@ The following table contains information about the arguments for `useController` The following table contains information about properties which `useController` produces. -| Object Name | Name | Type | Description | -| ------------ | -------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `field` | `onChange` | (value: any) => void | A function which sends the input's value to the library. It should be assigned to the `onChange` prop of the input and value should **not be `undefined`**. This prop updates [formState](/docs/useform/formstate) and you should avoid manually invoking [`setValue`](/docs/useform/setvalue) or other API related to field update. | -| `field` | `onBlur` | () => void | A function which sends the input's onBlur event to the library. It should be assigned to the input's `onBlur` prop. | -| `field` | `value` | unknown | The current value of the controlled component. | -| `field` | `disabled` | boolean | The disabled state of the input. | -| `field` | `name` | string | Input's name being registered. | -| `field` | `ref` | React.Ref | A ref used to connect hook form to the input. Assign `ref` to component's input ref to allow hook form to focus the error input. | -| `fieldState` | `invalid` | boolean | Invalid state for current input. | -| `fieldState` | `isTouched` | boolean | Touched state for current controlled input. | -| `fieldState` | `isDirty` | boolean | Dirty state for current controlled input. | -| `fieldState` | `isValidating` | boolean | Validating state for current controlled input. | -| `fieldState` | `error` | object | error for this specific input. | -| `formState` | `isDirty` | boolean | Set to `true` after the user modifies any of the inputs. **Important:** Make sure to provide all inputs' `defaultValues` at `useForm`, so hook form can have a single source of truth to compare whether the form is dirty. | -| `formState` | `dirtyFields` | object | An object with the user-modified fields. Make sure to provide all inputs' `defaultValues` via `useForm`, so the library can compare against the `defaultValues`. | -| `formState` | `touchedFields` | object | An object containing all the inputs the user has interacted with. | -| `formState` | `defaultValues` | object | The value that was set in [`useForm`](/docs/useform)'s `defaultValues` or updated `defaultValues` via [`reset`](/docs/useform/reset) API. | -| `formState` | `isSubmitted` | boolean | Set to `true` after the form is submitted. Will remain `true` until the `reset` method is invoked. | -| `formState` | `isSubmitSuccessful` | boolean | Indicates that the form was successfully submitted without any runtime error. | -| `formState` | `isSubmitting` | boolean | `true` if the form is currently being submitted. `false` otherwise. | -| `formState` | `isLoading` | boolean | `true` if the form is currently loading async default values. **Important:** this prop is only applicable to async `defaultValues`. | -| `formState` | `submitCount` | number | The number of times the form was submitted. | -| `formState` | `isValid` | boolean | Set to `true` if the form doesn't have any errors. `setError` immediately forces `isValid` to `false`; this value is not itself derived from validation and will be overwritten the next time validation runs (e.g. on the next `onChange`, submit, or `trigger()` call). | -| `formState` | `isValidating` | boolean | Set to `true` during validation. | -| `formState` | `validatingFields` | object | Captures fields that are undergoing asynchronous validation. | -| `formState` | `errors` | object | An object with field errors. There is also an [ErrorMessage](/docs/useformstate/errormessage) component to retrieve error messages easily. | -| `formState` | `disabled` | boolean | Set to `true` if the form is disabled via the `disabled` prop in [`useForm`](/docs/useform#disabled). | -| `formState` | `isReady` | boolean | Set to true when `formState` subscription setup is ready. | +| Object Name | Name | Type | Description | +| ------------ | -------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `field` | `onChange` | (value: any) => void | A function which sends the input's value to the library. It should be assigned to the `onChange` prop of the input and value should **not be `undefined`**. This prop updates [formState](/docs/useform/formstate) and you should avoid manually invoking [`setValue`](/docs/useform/setvalue) or other API related to field update. It accepts either a change event or a value: given an event, it reads `target.checked` for checkboxes, `target.files` for file inputs and `target.value` otherwise. Any object with a `target` property is treated as an event. | +| `field` | `onBlur` | () => void | A function which sends the input's onBlur event to the library. It should be assigned to the input's `onBlur` prop. | +| `field` | `value` | unknown | The current value of the controlled component. | +| `field` | `disabled` | boolean | The disabled state of the input. | +| `field` | `name` | string | Input's name being registered. | +| `field` | `ref` | React.Ref | A ref used to connect hook form to the input. Assign `ref` to component's input ref to allow hook form to focus the error input. | +| `fieldState` | `invalid` | boolean | Invalid state for current input. | +| `fieldState` | `isTouched` | boolean | Touched state for current controlled input. | +| `fieldState` | `isDirty` | boolean | Dirty state for current controlled input. | +| `fieldState` | `isValidating` | boolean | Validating state for current controlled input. | +| `fieldState` | `error` | object | error for this specific input. | +| `formState` | `isDirty` | boolean | Set to `true` after the user modifies any of the inputs. **Important:** Make sure to provide all inputs' `defaultValues` at `useForm`, so hook form can have a single source of truth to compare whether the form is dirty. | +| `formState` | `dirtyFields` | object | An object with the user-modified fields. Make sure to provide all inputs' `defaultValues` via `useForm`, so the library can compare against the `defaultValues`. | +| `formState` | `touchedFields` | object | An object containing all the inputs the user has interacted with. | +| `formState` | `defaultValues` | object | The value that was set in [`useForm`](/docs/useform)'s `defaultValues` or updated `defaultValues` via [`reset`](/docs/useform/reset) API. | +| `formState` | `isSubmitted` | boolean | Set to `true` after the form is submitted. Will remain `true` until the `reset` method is invoked. | +| `formState` | `isSubmitSuccessful` | boolean | Indicates that the form was successfully submitted without any runtime error. | +| `formState` | `isSubmitting` | boolean | `true` if the form is currently being submitted. `false` otherwise. | +| `formState` | `isLoading` | boolean | `true` if the form is currently loading async default values. **Important:** this prop is only applicable to async `defaultValues`. | +| `formState` | `submitCount` | number | The number of times the form was submitted. | +| `formState` | `isValid` | boolean | Set to `true` if the form doesn't have any errors. `setError` immediately forces `isValid` to `false`; this value is not itself derived from validation and will be overwritten the next time validation runs (e.g. on the next `onChange`, submit, or `trigger()` call). | +| `formState` | `isValidating` | boolean | Set to `true` during validation. | +| `formState` | `validatingFields` | object | Captures fields that are undergoing asynchronous validation. | +| `formState` | `errors` | object | An object with field errors. There is also an [ErrorMessage](/docs/useformstate/errormessage) component to retrieve error messages easily. | +| `formState` | `disabled` | boolean | Set to `true` if the form is disabled via the `disabled` prop in [`useForm`](/docs/useform#disabled). | +| `formState` | `isReady` | boolean | Set to true when `formState` subscription setup is ready. | ### Examples {#example} --- - +
@@ -217,6 +217,52 @@ export default function App() { } ``` +
+ +A generic component built on `useController` works with any form, and TypeScript still checks that `name` is a field of the form that `control` belongs to. + +```tsx copy +import { + useForm, + useController, + UseControllerProps, + FieldValues, +} from "react-hook-form" + +function TextInput(props: UseControllerProps) { + const { field, fieldState } = useController(props) + + return ( +
+ + {fieldState.error &&

{fieldState.error.message}

} +
+ ) +} + +type FormValues = { firstName: string; lastName: string } + +export default function App() { + const { control, handleSubmit } = useForm({ + defaultValues: { firstName: "", lastName: "" }, + }) + + return ( +
console.log(data))}> + + + + + ) +} +``` + +
+ ### Tips @@ -237,11 +283,23 @@ export default function App() { const [value, setValue] = useState(field.value); onChange={(event) => { - field.onChange(parseInt(event.target.value)) // data sent back to hook form + // data sent back to hook form; empty input becomes null instead of NaN + field.onChange(event.target.value === "" ? null : event.target.valueAsNumber) setValue(event.target.value) // UI state }} ``` +- **Focusing components that don't forward a DOM ref:** `field.ref` accepts any object with a `focus` method (and optionally `select`), so you can point it at the component's own focus API: + + ```javascript + + field.ref(instance && { focus: () => instance.focusInput() }) + } + /> + ``` + - Do not `register` the input again. This custom hook is designed to take care of the registration process. ```javascript diff --git a/src/content/docs/usecontroller/controller.mdx b/src/content/docs/usecontroller/controller.mdx index a060b6e2b..c696d536a 100644 --- a/src/content/docs/usecontroller/controller.mdx +++ b/src/content/docs/usecontroller/controller.mdx @@ -34,34 +34,34 @@ The following table contains information about the arguments for `Controller`. The following table contains information about properties which `Controller` produces. -| Object Name | Name | Type | Description | -| -------------------------------------- | -------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `field` | `onChange` | `(value: any) => void` | A function which sends the input's value to the library.

_It should be assigned to the `onChange` prop of the input and the value should **not be `undefined`**._
This prop updates [formState](/docs/useform/formstate) and you should avoid manually invoking [setValue](/docs/useform/setvalue) or other APIs related to field updates. | -| `field` | `onBlur` | `() => void` | A function which sends the input's onBlur event to the library. It should be assigned to the input's `onBlur` prop. | -| `field` | `value` | unknown | The current value of the controlled component. | -| `field` | `disabled` | boolean | The disabled state of the input. | -| `field` | `name` | string | Input's name being registered. | -| `field` | `ref` | React.Ref | A ref used to connect hook form to the input. Assign `ref` to component's input ref to allow hook form to focus the error input — this only works if the target component forwards refs (`React.forwardRef`) or exposes an equivalent, like MUI's `inputRef`. | -| `fieldState` | `invalid` | boolean | Invalid state for current input. | -| `fieldState` | `isTouched` | boolean | Touched state for current controlled input. | -| `fieldState` | `isDirty` | boolean | Dirty state for current controlled input. | -| `fieldState` | `isValidating` | boolean | Validating state for current controlled input. | -| `fieldState` | `error` | object | error for this specific input. | -| [`formState`](/docs/useform/formstate) | `isDirty` | boolean | Set to `true` after the user modifies any of the inputs.
  1. **Important:** Make sure to provide all inputs' defaultValues at the useForm level, so the library can have a single source of truth to compare whether the form is dirty.
  2. File-type inputs will need to be managed at the app level due to the ability to cancel file selection and [FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList) object.
| -| `formState` | `dirtyFields` | object | An object with the user-modified fields. Make sure to provide all inputs' defaultValues via useForm, so the library can compare against the `defaultValues`
  1. **Important:** Make sure to provide defaultValues at the useForm level, so the library can have a single source of truth to compare each field's dirtiness.
  2. Dirty fields will **not** represent as `isDirty` formState, because dirty fields are marked field dirty at the field level rather than the entire form. If you want to determine the entire form state use `isDirty` instead.
| -| `formState` | `touchedFields` | object | An object containing all the inputs the user has interacted with. | -| `formState` | `defaultValues` | object | The value that was set in [useForm](/docs/useform)'s `defaultValues` or updated `defaultValues` via the [reset](/docs/useform/reset) API. | -| `formState` | `isSubmitted` | boolean | Set to `true` after the form has been submitted. Will remain `true` until the `reset` method is invoked. | -| `formState` | `isSubmitSuccessful` | boolean | Indicates that the form was successfully submitted without any runtime error. | -| `formState` | `isSubmitting` | boolean | `true` if the form is currently being submitted. `false` otherwise. | -| `formState` | `isLoading` | boolean | `true` if the form is currently loading async default values.
**Important:** this prop is only applicable to async `defaultValues` | -| `formState` | `submitCount` | number | The number of times the form was submitted. | -| `formState` | `isValid` | boolean | Set to `true` if the form doesn't have any errors.

`setError` immediately forces `isValid` to `false`; this value is not itself derived from validation and will be overwritten the next time validation runs (e.g. on the next `onChange`, submit, or `trigger()` call). | -| `formState` | `isValidating` | boolean | Set to `true` during validation. | -| `formState` | `validatingFields` | object | Captures fields that are undergoing asynchronous validation. | -| `formState` | `errors` | object | An object with field errors. There is also an [ErrorMessage](/docs/useformstate/errormessage) component to retrieve error messages easily. | -| `formState` | `disabled` | boolean | Set to `true` if the form is disabled via the `disabled` prop in [`useForm`](/docs/useform#disabled). | -| `formState` | `isReady` | boolean | Set to true when `formState` subscription setup is ready. | +| Object Name | Name | Type | Description | +| -------------------------------------- | -------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `field` | `onChange` | `(value: any) => void` | A function which sends the input's value to the library.

_It should be assigned to the `onChange` prop of the input and the value should **not be `undefined`**._
This prop updates [formState](/docs/useform/formstate) and you should avoid manually invoking [setValue](/docs/useform/setvalue) or other APIs related to field updates. It accepts either a change event or a value: given an event, it reads `target.checked` for checkboxes, `target.files` for file inputs and `target.value` otherwise. Any object with a `target` property is treated as an event. | +| `field` | `onBlur` | `() => void` | A function which sends the input's onBlur event to the library. It should be assigned to the input's `onBlur` prop. | +| `field` | `value` | unknown | The current value of the controlled component. | +| `field` | `disabled` | boolean | The disabled state of the input. | +| `field` | `name` | string | Input's name being registered. | +| `field` | `ref` | React.Ref | A ref used to connect hook form to the input. Assign `ref` to component's input ref to allow hook form to focus the error input — this only works if the target component forwards refs (`React.forwardRef`) or exposes an equivalent, like MUI's `inputRef`. | +| `fieldState` | `invalid` | boolean | Invalid state for current input. | +| `fieldState` | `isTouched` | boolean | Touched state for current controlled input. | +| `fieldState` | `isDirty` | boolean | Dirty state for current controlled input. | +| `fieldState` | `isValidating` | boolean | Validating state for current controlled input. | +| `fieldState` | `error` | object | error for this specific input. | +| [`formState`](/docs/useform/formstate) | `isDirty` | boolean | Set to `true` after the user modifies any of the inputs.
  1. **Important:** Make sure to provide all inputs' defaultValues at the useForm level, so the library can have a single source of truth to compare whether the form is dirty.
  2. File-type inputs will need to be managed at the app level due to the ability to cancel file selection and [FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList) object.
| +| `formState` | `dirtyFields` | object | An object with the user-modified fields. Make sure to provide all inputs' defaultValues via useForm, so the library can compare against the `defaultValues`
  1. **Important:** Make sure to provide defaultValues at the useForm level, so the library can have a single source of truth to compare each field's dirtiness.
  2. Dirty fields will **not** represent as `isDirty` formState, because dirty fields are marked field dirty at the field level rather than the entire form. If you want to determine the entire form state use `isDirty` instead.
| +| `formState` | `touchedFields` | object | An object containing all the inputs the user has interacted with. | +| `formState` | `defaultValues` | object | The value that was set in [useForm](/docs/useform)'s `defaultValues` or updated `defaultValues` via the [reset](/docs/useform/reset) API. | +| `formState` | `isSubmitted` | boolean | Set to `true` after the form has been submitted. Will remain `true` until the `reset` method is invoked. | +| `formState` | `isSubmitSuccessful` | boolean | Indicates that the form was successfully submitted without any runtime error. | +| `formState` | `isSubmitting` | boolean | `true` if the form is currently being submitted. `false` otherwise. | +| `formState` | `isLoading` | boolean | `true` if the form is currently loading async default values.
**Important:** this prop is only applicable to async `defaultValues` | +| `formState` | `submitCount` | number | The number of times the form was submitted. | +| `formState` | `isValid` | boolean | Set to `true` if the form doesn't have any errors.

`setError` immediately forces `isValid` to `false`; this value is not itself derived from validation and will be overwritten the next time validation runs (e.g. on the next `onChange`, submit, or `trigger()` call). | +| `formState` | `isValidating` | boolean | Set to `true` during validation. | +| `formState` | `validatingFields` | object | Captures fields that are undergoing asynchronous validation. | +| `formState` | `errors` | object | An object with field errors. There is also an [ErrorMessage](/docs/useformstate/errormessage) component to retrieve error messages easily. | +| `formState` | `disabled` | boolean | Set to `true` if the form is disabled via the `disabled` prop in [`useForm`](/docs/useform#disabled). | +| `formState` | `isReady` | boolean | Set to true when `formState` subscription setup is ready. | ##### Examples: @@ -224,19 +224,32 @@ The following video showcases what's inside Controller and how it was built. /> ``` -- Customise what value gets sent to hook form by transforming the value during `onChange`. +- Customise what value gets sent to hook form by transforming the value during `onChange`. For number inputs, map an empty input to `null` instead of `NaN`, and render `null` as an empty string. `rules` doesn't support `valueAsNumber`, so convert the value yourself. + ```javascript { - // sending integer instead of string. - return ( - field.onChange(parseInt(e.target.value))} - /> - ) - }} + name="age" + render={({ field }) => ( + + field.onChange(e.target.value === "" ? null : e.target.valueAsNumber) + } + /> + )} + /> + ``` + +- **Focusing components that don't forward a DOM ref:** `field.ref` accepts any object with a `focus` method (and optionally `select`), so you can point it at the component's own focus API: + + ```javascript + + field.ref(instance && { focus: () => instance.focusInput() }) + } /> ``` diff --git a/src/content/docs/usefieldarray.mdx b/src/content/docs/usefieldarray.mdx index 3e65d65a8..ad07f67ca 100644 --- a/src/content/docs/usefieldarray.mdx +++ b/src/content/docs/usefieldarray.mdx @@ -167,7 +167,7 @@ function FieldArray() { --- - +
@@ -477,6 +477,92 @@ const App = () => { } ``` +```tsx copy +import { + useForm, + useFieldArray, + Control, + UseFormRegister, +} from "react-hook-form" + +type FormValues = { + teams: { + name: string + members: { email: string }[] + }[] +} + +function Members({ + teamIndex, + control, + register, +}: { + teamIndex: number + control: Control + register: UseFormRegister +}) { + const { fields, append, remove } = useFieldArray({ + control, + name: `teams.${teamIndex}.members`, + }) + + return ( +
    + {fields.map((field, memberIndex) => ( +
  • + + +
  • + ))} + +
+ ) +} + +export default function App() { + const { control, register, handleSubmit } = useForm({ + defaultValues: { + teams: [{ name: "", members: [{ email: "" }] }], + }, + }) + const { fields, append, remove } = useFieldArray({ + control, + name: "teams", + }) + + return ( +
console.log(data))}> + {fields.map((field, teamIndex) => ( +
+ + + +
+ ))} + + +
+ ) +} +``` + ### Video @@ -534,6 +620,8 @@ function App() { There will be cases where you want to control the entire field array, which means each onChange reflects on the `fields` object. +If you only need to display values from the array, such as a live total, read them with [`useWatch`](/docs/usewatch) (`useWatch({ control, name: "fieldArray" })`) in a child component instead, so only that component re-renders. + ```tsx sandbox="https://codesandbox.io/s/infallible-bush-c92l0?file=/src/App.tsx" import { useForm, useFieldArray } from "react-hook-form" diff --git a/src/content/docs/useform.mdx b/src/content/docs/useform.mdx index c7f634153..ca0d655c7 100644 --- a/src/content/docs/useform.mdx +++ b/src/content/docs/useform.mdx @@ -171,6 +171,21 @@ useForm({ - You **should avoid** providing `undefined` as a default value, as it conflicts with the default state of a controlled component. - `defaultValues` are cached. To reset them, use the [reset](/docs/useform/reset) API. +- An async `defaultValues` function runs once, when the form mounts. If its promise rejects, `formState.isLoading` stays `true`, so catch errors inside the function and return fallback values: + + ```javascript + useForm({ + defaultValues: async () => { + try { + const res = await fetch("/api/user") + return await res.json() + } catch { + return { firstName: "", lastName: "" } + } + }, + }) + ``` + - `defaultValues` will be included in the submission result by default. - It's recommended to avoid using custom objects containing prototype methods, such as `Moment` or `Luxon`, as `defaultValues`. - There are other options for including form data: @@ -219,6 +234,22 @@ function App() { } ``` + + +- `values` is deep-compared with the previous `values` prop. When it changes, the form is reset with the new values, which **replaces anything the user has typed**. If your data source refetches in the background (for example, React Query on window focus) and returns updated data, set `resetOptions: { keepDirtyValues: true }` to keep the user's edits. +- Passing a new object with the same content does nothing, so you don't need to memoize `values`. + + + +##### Loading form data from a server: which option to use? + +| Approach | Updates when the data changes | Good for | +| --------------------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| Async [`defaultValues`](#defaultValues) | No, it runs once on mount | Edit forms that load their data once. `formState.isLoading` is `true` until it resolves. | +| [`values`](#values) | Yes, it resets the form whenever the data changes | Forms that must stay in sync with external or server state. Combine with [`resetOptions`](#resetOptions) to keep user edits. | +| [`reset`](/docs/useform/reset) in `useEffect` | Only when your effect runs | Full control over when the form is refilled and which state to keep. | +| Render the form after the data arrives | No | The simplest option: fetch in the parent and pass the data in as `defaultValues`. | + #### errors: FieldErrors {#errors} --- diff --git a/src/content/docs/useform/formstate.mdx b/src/content/docs/useform/formstate.mdx index 635335d74..139d4dfb6 100644 --- a/src/content/docs/useform/formstate.mdx +++ b/src/content/docs/useform/formstate.mdx @@ -26,10 +26,10 @@ This object contains information about the entire form state. It helps you keep | `defaultValues` | object | The value that was set at [useForm](/docs/useform)'s `defaultValues` or updated `defaultValues` via the [reset](/docs/useform/reset) API. | | `isSubmitted` | boolean | Set to `true` after the form is submitted. Will remain `true` until the `reset` method is invoked. | | `isSubmitSuccessful` | boolean | Indicates that the form was successfully submitted without any runtime error. | -| `isSubmitting` | boolean | `true` if the form is currently being submitted. `false` otherwise. | +| `isSubmitting` | boolean | `true` if the form is currently being submitted. `false` otherwise.
  • It stays `true` only while the promise returned by your `onSubmit` handler is pending. If you start a request without `await`ing or returning it, `isSubmitting` turns `false` straight away.
| | `isLoading` | boolean | `true` if the form is currently loading async default values.
  • **Important:** this prop is only applicable to async `defaultValues` await fetch('/api')\n})`}/>
| | `submitCount` | number | The number of times the form has been submitted. | -| `isValid` | boolean | Set to `true` if the form doesn't have any errors.
  • `setError` immediately forces `isValid` to `false`; this value is not itself derived from validation and will be overwritten the next time validation runs (e.g. on the next `onChange`, submit, or `trigger()` call).
| +| `isValid` | boolean | Set to `true` if the form doesn't have any errors.
  • While `isValid` is subscribed, every field (or the whole [resolver](/docs/useform#resolver) schema) is validated on mount and on each change, whatever the [`mode`](/docs/useform#mode). `errors` only fills in according to `mode` and `reValidateMode`, so with the default `mode: "onSubmit"` `isValid` can be `false` while `errors` is still empty.
  • `isValid` is not computed while the form is [`disabled`](/docs/useform#disabled).
  • `setError` immediately forces `isValid` to `false`; this value is not itself derived from validation and will be overwritten the next time validation runs (e.g. on the next `onChange`, submit, or `trigger()` call).
| | `isValidating` | boolean | Set to `true` during validation. | | `validatingFields` | object | Captures fields that are undergoing asynchronous validation. | | `errors` | object | An object with field errors. There is also an [ErrorMessage](/docs/useformstate/errormessage) component to retrieve error messages easily. | diff --git a/src/content/docs/useform/getvalues.mdx b/src/content/docs/useform/getvalues.mdx index 4a98974f5..99df94814 100644 --- a/src/content/docs/useform/getvalues.mdx +++ b/src/content/docs/useform/getvalues.mdx @@ -45,6 +45,8 @@ The example below shows what to expect when you invoke `getValues` method. - It will return `defaultValues` from `useForm` before the **initial** render. +- `getValues` returns the raw field values. The data passed to your `handleSubmit` callback can differ: with a [resolver](/docs/useform#resolver) it's the resolver's output (with any transforms or coercion applied), and disabled fields are left out. +- `getValues` doesn't subscribe to changes, so it won't re-render your component. To render a value, use [`useWatch`](/docs/usewatch) or [`watch`](/docs/useform/watch). diff --git a/src/content/docs/useform/handlesubmit.mdx b/src/content/docs/useform/handlesubmit.mdx index 148494613..36021b8c7 100644 --- a/src/content/docs/useform/handlesubmit.mdx +++ b/src/content/docs/useform/handlesubmit.mdx @@ -29,6 +29,14 @@ This function will receive the form data if form validation is successful. const result = await handleSubmit(onValid)() // result: { id: string } | undefined ``` +- `formState.isSubmitting` stays `true` until the promise returned by `onSubmit` settles, so `await` (or return) your async work: + + ```javascript + const onSubmit = async (data) => { + await fetch("/api", { method: "POST", body: JSON.stringify(data) }) // ✅ isSubmitting stays true until this resolves + } + ``` + - `disabled` inputs will appear as `undefined` values in form values. If you want to prevent users from updating an input and wish to retain the form value, you can use `readOnly` or disable the entire <fieldset />. Here is an [example](https://codesandbox.io/s/react-hook-form-disabled-inputs-oihxx). - `handleSubmit` function will not swallow errors that occurred inside your onSubmit callback, so we recommend that you use try/catch blocks inside your async requests and handle those errors gracefully for your customers. Use `setError` inside the catch block to register a server-side error — this also ensures `formState.isSubmitSuccessful` is set to `false`. @@ -48,6 +56,28 @@ This function will receive the form data if form validation is successful. return
``` +- To submit from a button outside the ``, either link the button to the form with the native `form` attribute, or call the returned function directly: + + ```javascript + ...
+ + // ✅ native form attribute + // ✅ call handleSubmit directly + ``` + +- React's `submit` event bubbles through the component tree, including portals. If a form is rendered inside another form (for example in a modal), stop propagation so the outer form doesn't submit too: + + ```javascript +
{ + event.stopPropagation() + handleSubmit(onInnerSubmit)(event) + }} + /> + ``` + +- For submitting with React Server Actions or `useActionState`, see [Server Actions](/advanced-usage#ServerActionsuseActionState). To test your submit flow, see [Testing Form](/advanced-usage#TestingForm). + ##### Examples: diff --git a/src/content/docs/useform/register.mdx b/src/content/docs/useform/register.mdx index bfa9d1102..adb102ef3 100644 --- a/src/content/docs/useform/register.mdx +++ b/src/content/docs/useform/register.mdx @@ -249,6 +249,46 @@ export default function App() { } ``` +**File input** + +A file input's value is a `FileList`, not a string. `required` works as usual, use `validate` to check things like file size, and build a `FormData` from the files when you submit. + +```tsx copy +import { useForm } from "react-hook-form" + +type FormValues = { avatar: FileList } + +export default function App() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm() + + const onSubmit = async ({ avatar }: FormValues) => { + const body = new FormData() + body.append("avatar", avatar[0]) + await fetch("/api/avatar", { method: "POST", body }) + } + + return ( + + + !files?.[0] || files[0].size < 2_000_000 || "Max size is 2 MB", + })} + /> + {errors.avatar &&

{errors.avatar.message}

} + + + ) +} +``` + ### Video --- @@ -302,3 +342,5 @@ const firstName = register('firstName', { required: true }) inputRef={firstName.ref} // you can achieve the same for different ref names such as innerRef /> ``` + +If you also need the input's ref yourself, see [How to share ref usage](/faqs#Howtosharerefusage). diff --git a/src/content/docs/useform/reset.mdx b/src/content/docs/useform/reset.mdx index d9fd85c88..d54afd600 100644 --- a/src/content/docs/useform/reset.mdx +++ b/src/content/docs/useform/reset.mdx @@ -35,6 +35,7 @@ Reset the entire form state, fields reference, and subscriptions. There are opti - It is recommended to always provide defaultValues when resetting a form to ensure all inputs, especially controlled components, are restored correctly. +- Avoid calling `reset` inside `onSubmit`. `handleSubmit` updates `isSubmitted`, `isSubmitSuccessful` and `submitCount` after your handler finishes, so a `reset` there clears the values but not the submission state. Reset in a `useEffect` when `formState.isSubmitSuccessful` becomes `true` instead (see the **Submit with Reset** example below). - Calling `reset` with `values` updates the form's `defaultValues` unless `options.keepDefaultValues` is set. If `reset` is later called without `values` or with `{}`, the form resets to the last `values` provided instead of the initial `defaultValues`. diff --git a/src/content/docs/useform/setvalue.mdx b/src/content/docs/useform/setvalue.mdx index b4fd040d5..586a53dc5 100644 --- a/src/content/docs/useform/setvalue.mdx +++ b/src/content/docs/useform/setvalue.mdx @@ -24,6 +24,16 @@ This function allows you to dynamically set the value of a registered +- By default, `setValue` only updates the value and notifies [`watch`](/docs/useform/watch), [`useWatch`](/docs/usewatch) and [`subscribe`](/docs/useform/subscribe). It does **not** validate the field (even after the form has been submitted) or mark it as dirty or touched. Pass the options you need: + + ```javascript + setValue("firstName", "Bill", { + shouldValidate: true, // update errors and isValid + shouldDirty: true, // update dirtyFields and isDirty + shouldTouch: true, // update touchedFields + }) + ``` + - `shouldDirty` only guarantees that the target field is marked in `dirtyFields` immediately. Because `isDirty` always compares current values against `defaultValues`, a later update to _any_ field can trigger a `dirtyFields` recompute that also surfaces other fields whose value already differs from their default — including ones last written with `shouldDirty: false`. - You can use methods such as [replace](/docs/usefieldarray#return) or [update](/docs/usefieldarray#return) for a field array; however, they will cause the component to unmount and remount for the targeted field array. @@ -83,14 +93,19 @@ import { useForm } from "react-hook-form" const App = () => { const { register, setValue } = useForm({ - firstName: "", + defaultValues: { + firstName: "", + }, }) return (
- +