import { useForm } from '@uhg-abyss/web/hooks/useForm';Usage
Use the useForm hook along with the FormProvider in order to better manage your forms and fully utilize the capabilities of form management within Abyss.
Below is a simple example of a form built with useForm. Try filling out the inputs (or not) and submitting the form to see how it works!
Handle submit
The onSubmit and onError props of FormProvider allow you to handle form submission and errors. The onSubmit callback will be called when the form is submitted and passes validation, while the onError callback will be called when the form is submitted but fails validation. Both callbacks receive the form data and the submit event as arguments.
Parameters
Default values
The defaultValues parameter populates the entire form with default values. It supports both synchronous and asynchronous assignments of default values.
Values
The values parameter reacts to changes and updates the form values, which is useful when a form needs to be updated with external data.
This can be done synchronously:
const MyForm = ({ values }) => { const form = useForm({ values, // will get updated when values props updates });
// ...};Or asynchronously:
const MyForm = () => { const values = await useFetch('/api');
const form = useForm({ defaultValues: { firstName: '', lastName: '', }, values, // will get updated once `values` returns });
// ...};Disabled
The disabled parameter, if true, will disable all inputs within the form. The default value is false.
Note: When disabled is true, the "Submit" button is not disabled, but the form will not submit or validate and thus, the onSubmit callback will not be called. Because of this, it is recommended to also disable the "Submit" button (using the isDisabled prop) when the form is disabled to avoid confusion for users.
Returned object
The useForm hook returns an object with the following properties and methods.
Form state
This formState object contains information about the current state of the form. It contains the following properties along with a number of methods (described in the following sections):
errors: An object with field errors.isDirty: Set to true after the user modifies any of the inputs.isValid: Set to true if the form doesn't have any errors.isValidating: Set to true during validation.isSubmitting: true if the form is currently being submitted; false if otherwise.isSubmitted: Set to true after the form is submitted.isSubmitSuccessful: Indicate the form was successfully submitted without any Promise rejection or Error being thrown within the handleSubmit callback.submitCount: Number of times the form was submitted.touchedFields: An object containing all the inputs the user has interacted with.dirtyFields: An object with the user-modified fields.
When subscribing to formState in a useEffect callback, make sure to place the entire formState object in the dependencies array.
() => { const form = useForm();
useEffect(() => { // Do something with `formState` }, [form.formState]);
// ...};Watch
The watch method will watch specified inputs and return their values. You can watch a single input, multiple inputs, or the entire form. When the value(s) of the watched input(s) change, the component will re-render.
Validate
The validate method allows you to manually trigger validation for specific fields. It takes the field model as the first argument, a success callback as the second argument, and an error callback as the third argument.
Reset
The reset method allows you to reset some or all of the form state.
Note: When invoking reset({ value }) without supplying defaultValues via useForm, the library will replace defaultValues with a shallow clone value object that you provide (not a deep clone).
Avoid doing the following, as it can lead to unexpected behavior due to shared references:
const defaultValues = { object: { deepNest: { file: new File(), }, },};
useForm({ defaultValues });reset(defaultValues); // ❌It's safer to create a new object, even if the values are the same, to ensure that there are no shared references:
useForm({ deepNest: { file: new File(), },});
reset({ deepNest: { file: new File(), // ✅ },});Set error
The setError method allows you to manually set one or more field errors.
Clear errors
The clearErrors method allows you to clear one or more field errors. If no arguments are provided, it will clear all errors.
Set value
The setValue method allows you to dynamically set the value of a registered field and attempts to avoid unnecessary re-renders by only updating the specific field that changed.
Set focus
The setFocus method allows you to programmatically focus on an input by model.
Get values
The getValues method is an optimized helper for reading form values. The difference between watch and getValues is that getValues will not trigger re-renders or subscribe to input changes.
Trigger
The trigger method allows you to manually trigger validation on the form or specific fields. This method is also useful when you have dependent validation (i.e., when one input's validation depends on the value of another input). For an example of this, see the Cross-field validation example below.
Validation strategy
There are two different validation strategies:
mode: Validation strategy to use before submitting the form (default:'onSubmit').reValidateMode: Validation strategy to use after submitting the form (default:'onChange').
Teams should only use the following options:
mode:'onChange'|'onSubmit'reValidateMode:'onChange'|'onSubmit'
Disclaimer: Using other validation strategies can lead to inconsistent behavior.
Cross-field validation example
This example shows how to use the trigger method to create cross-field/dependent validation. In this example, the "Middle Name" field is only required if the "No Middle Name" checkbox is not checked. When the checkbox is toggled, it triggers validation on the "Middle Name" field to ensure that the error message is displayed or removed accordingly.
Form input autocomplete
To enable browser autocompletion, the autoComplete prop must be enabled on the FormProvider component (with the value "on") as well as the individual input components. The value of the autoComplete prop on the input components should be set to the appropriate autocomplete attribute value that corresponds to the type of data being collected (e.g., given-name for first name, family-name for last name, email for email address, etc.). This allows browsers to recognize the type of information being requested and provide relevant autocomplete suggestions to users.
See the Mozilla Developer Network docs for more information.
Autofill off
Additional documentation
@uhg-abyss/web/hooks/useForm is a built on top of the useForm hook from React Hook Form. Teams that already leverage or want to use React Hook Form in other spots in their applications can use the @uhg-abyss/web/tools/reactHookFormTools package.
This package ensures you are using the same version of React Hook Form across the application and can also be used to grab type information directly.
Note: You should be using Abyss's useForm hook when using Abyss components and not React Hook Form's.
import type { SubmitHandler } from '@uhg-abyss/web/tools/reactHookFormTools';
interface FormData { firstName: string;}
// ...
const handleSubmit: SubmitHandler<FormData> = (data) => { console.log('data', data);};
// ...Using TypeScript with useForm gives you strong type checking for your form data structure.
interface FormData { firstName: string; lastName: string;}
const form = useForm<FormData>({ defaultValues: { firstName: '', lastName: '', pizza: 'cheese', // Throws TS error since pizza is not a field in FormData },});
// Throws TS error since pizza is not a field in FormDataconst watchField = form.watch('pizza');
// Doesn't throw TS error since firstName and lastName are fields in FormDataconst watchFields = form.watch(['firstName', 'lastName']);