formState
Access real-time form state properties.
</> formState: FormState
This object contains information about the entire form state. It helps you keep track of user interactions with your form application.
Return
| Name | Type | Description |
|---|---|---|
isDirty | boolean | Set to true after the user modifies any of the inputs.
|
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.
|
touchedFields | object | An object containing all the inputs the user has interacted with. |
defaultValues | object | Since v7.37.0 The value that was set at useForm's defaultValues or updated defaultValues via the 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.
|
isLoading | boolean | Since v7.41.0 true if the form is currently loading async default values.
|
submitCount | number | The number of times the form has been submitted. |
isValid | boolean | Set to true if the form doesn't have any errors.
|
isValidating | boolean | Set to true during validation. |
validatingFields | object | Since v7.51.0 Captures fields that are undergoing asynchronous validation. |
errors | object | An object with field errors. There is also an ErrorMessage component to retrieve error messages easily. |
disabled | boolean | Since v7.48.0 Set to true if the form is disabled via the disabled prop in useForm. |
isReady | boolean | Since v7.56.0 Set to true when formState subscription setup is ready.
|
Returned
formStateis wrapped with a Proxy to improve render performance and skip extra logic if a specific state is not subscribed to. Therefore, make sure you invoke or read it before a render in order to enable the state update.formStateis updated in batch. If you want to subscribe toformStateviauseEffect, make sure that you place the entireformStatein the optional array.useEffect(() => {if (formState.errors.firstName) {// do your logic here}}, [formState]) // ✅// ❌ [formState.errors] will not trigger the useEffectimport { useForm } from "react-hook-form";export default function App () {const {register,handleSubmit,formState} = useForm();const onSubmit = (data) => console.log(data);React.useEffect(() => {console.log("touchedFields", formState.touchedFields);},[formState]); // use entire formState object as optional array arg in useEffect, not individual properties of itreturn (<form onSubmit={handleSubmit(onSubmit)}><input {...register("test")} /><input type="submit" /></form>);};Pay attention to the logical operator when subscribing to
formState.// ❌ formState.isValid is accessed conditionally,// so the Proxy does not subscribe to changes of that statereturn <button disabled={!formState.isDirty || !formState.isValid} />;// ✅ read all formState values to subscribe to changesconst { isDirty, isValid } = formState;return <button disabled={!isDirty || !isValid} />;
Examples
import { useForm } from "react-hook-form";export default function App() {const {register,handleSubmit,// Read the formState before render to subscribe the form state through the ProxyformState: { errors, isDirty, isSubmitting, touchedFields, submitCount },} = useForm();const onSubmit = (data) => console.log(data);return (<form onSubmit={handleSubmit(onSubmit)}><input {...register("test")} /><input type="submit" /></form>);}
Video
Tips
- Read
formStatein the component that uses it. To show a submit button's state without re-rendering the whole form, move the button into a child component that callsuseFormState, or use theFormStatecomponent. - A common submit button pattern is
disabled={!isDirty || isSubmitting}. It prevents an empty submission and a double submission. Remember to destructure both values before you use them. - Use
isSubmitSuccessfulto show a confirmation message, or to reset the form in auseEffectafter a successful submission (seereset). errorsholds what is currently stored, not what is valid right now. To check validity without showing errors, useisValid. To validate on demand, usetrigger.
Troubleshooting
isDirty stays true after the user clears the field
Why it happens
isDirty compares the current values with defaultValues. A field without a default value starts as undefined, and after the user types and deletes the text its value is "". That is not equal to undefined, so the field is still dirty.
Fix
Give every field a default value in useForm, even if it is just "":
useForm({ defaultValues: { firstName: "" } })
A formState value is stale when you read it in a handler
Why it happens
formState only tracks the values your component reads during render. A value you read only inside an event handler or effect is never subscribed to.
Fix
Destructure the value during render, as in const { formState: { isValid } } = useForm(). See useForm for an example.