React-hook-form 1: register, formState and watch.
The React-Hook-Form is a form management library for React. It minimizes re-renders by leveraging the native form validation API and easily integrates with other UI libraries. It features a simple API structure through the useForm() hook, which offers methods for handling forms, it supports both synchronous and asynchronous validation, along with built-in error handling.
The register() method from the useForm() hook tracks each input name and value in the form state. On submit the handleSubmit() function will make the form data available to its callback function argument.
We manage multiple independent forms by creating different instances of the useForm() methods.
npm install react-hook-form
import { useForm } from "react-hook-form"
//The inputs maintain their built-in attributes
const { register, handleSubmit } = useForm()
const { register: register1, handleSubmit: handleSubmit1} = useForm();
return(
<div className="m-5">
<form onSubmit={handleSubmit( ((data)=> console.log(data)) )}>
<input {...register("prima")} />
<input type="submit" />
</form>
<form onClick={handleSubmit1( ((data)=> console.log(data)) )}>
<input {...register1("prima")} />
<input type="submit" />
</form>
</div>
)
The useForm() hook configures the form behavior through its options object properties.
//External values can be used on the useForm() props values
let [valori, setValori] = useState({})
let [sbagli, setSbagli] = useState({})
const {
register, handleSubmit, formState: { errors, isValid },
} = useForm({
mode: "onSubmit" //onChange | onBlur | onTouched | all
reValidateMode: "onChange" //onBlur | onSubmit = 'onChange'
defaultValues: async () => {
let prezzo = await fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
let risultato = await prezzo.json()
return {
primo: "unico",
secondo: risultato.chartName
}
},
criteriaMode. "firstError" // "all"
values: valori,
errors: sbagli,
criteriaMode: "firstError" //"all"
shouldFocusError: true,
delayError: 0,
shouldUnregister: true,
disable: false
})
We edit useForm() properties using their assigned state values.
//Destruct the actual errors object and not the returned state
<button onClick={()=> setValori(()=> ({primo: "new", secondo: "values"}) )}>
Set input values
</button>
<button onClick={()=> setSbagli((erro)=> ({...errors, secondo: "new error"}))>
Add error
</button>
The useForm() register function
The register() function, returned by useForm(), registers a field into the form state. It returns an object that, when destructured within an <input> element, provides all the necessary props and event handlers from the useForm() hook. The input's name is used as an argument for the register function to link the input to the form state. We can also include a configuration options object as an argument to the register function.
The register() function can be destructured outside of the input tag. Any function returned by the register event handler needs to be called on each native input event to ensure that the updated values are available in the form state.
//The input needs the same name and ref as the destructed register
const { register, handleSubmit } = useForm()
const {onChange: cambio, ref: ref2} = register("secondo")
function doppio(event){
//custom and original onChange
console.log(event.target.value)
cambio(event)
}
const valori = (data) => {console.log(data)}
<form onSubmit={handleSubmit(valori)}>
<input {...register("primo", {onChange: ()=>{console.log("pressed")} })}/>
<input name="secondo" ref={ref2} onChange={doppio}/>
<input type="submit" />
</form>
By using specific naming conventions, we can create object structures based on the input names.
//If a number is used, it will be treated as an element of an array.
<form>
<input {...register("primo.uno")}/>
<input {...register("secondo.2")}/>
<form/>
{ primo: {uno: "valore"}, secondo: [, , "valore"] }
The register options object includes all built-in validation rules. The onBlur() event handler triggers each time the user focuses away from the input.
//Use the useForm() value prop to set input values
//We can return strings with the built in validation rules.
const [luce, setLuce] = useState(0)
<input {...register("primo", {
required: {
value: true,
message: "The input is empty"
},
minLength: 5, maxLength: 10, pattern: /^[a-zA-Z]+$/,
value: luce,
onBlur: (event) => {
console.log("Past input:", event.target.value);
},
})}/>
The valueAsNumber and valueAsDate properties will parse the input data into their respective types. Any input value in the form data is a string, regardless of the input type attribute. If the input values cannot be parsed, it will return NaN. The type attribute in the input element controls the allowed input types and interactions within the browser, preventing the entry of other types of input, but it still returns strings to the js.
The shouldUnregister prop applies to the single input upon unmounting. The disabled prop results in undefined for the input values. To avoid this, we can use the placeholder attribute on the input.
//do not use shouldUnregister with useFieldArray due to execution order
//Use readOnly or disabled fieldset to return values from disabled inputs
<form>
<input type="number/date" {...register("primo", {
shouldUnregister: false, //true
valueAsNumber: true, valueAsDate: true,
})} />
<input {...register("secondo")} readOnly placeholder="not changeable value"/>
<fieldset disabled>
<input {...register("terzo")} placeholder="First Name" />
</fieldset>
<input type="submit" />
</form>
The setValueAs prop modifies the input value before the validation process. It is ignored if the valueAsNumber or valueAsDate are present.
The validate property contains the validation rule functions. You can validate using either a single callback function or an object with multiple function properties. Any returned strings will be added to the error object under the types property. You can return false to indicate a validation error. The validate property supports asynchronous functions for validation.
The setValueAs edited input can skip some validate property functions.
//Use criteriaMode: "all" on useForm() to return multiple errors
<form>
<input {...register("primo", {
setValueAs: (value) => ( value + "/api.com" )
validate: {
lungo: (value) => value.length < 5 && "Too short",
corto: v => v.length < 10 && false
}
})} />
</form>

We can modify the properties of a registered input option by re-declaring the register() function with the input's name.
//It can be declared as a result of any event.
function cambio(event){
event.target.checked ?? register('primo',{ required: true }) : register('primo');
}
<form>
<p> {errors.primo && "It is required"} </p>
<input {...register("primo")} /> <span> Required? </span>
<input {...register("check", { onChange: (e)=> (cambio(e)) } )} type="checkbox" />
</form>

The unregister() method will manually remove an unmounted input from the form state. And any validation rules associated with the unregistered input will be removed. Its options props are here.
//It aplies to the input name, instead of using shouldUnregister prop
//If just un-registered the input will be re-registered on render
const [vede, setVede] = useState(true)
function togli(){
setVede(false); unregister("primo.uno")
}
<form>
<button onClick={togli}> Un-register </button>
{vede && <input {...register("primo.uno")} />}
</form>
The formState useForm() object.
The returned formState object contains information about the form, it allows you to track user interactions, validation status, and any errors that may occur during form submission.
//We can create instances of the formState properties
const { register, formState: { isDirty } } = useForm();
const { register: register1, formState: { isDirty: isDirty1 } } = useForm();
The formState properties are updated in batches, meaning they are held and do not trigger a re-render until all updates are processed. Therefore, when subscribing to formState properties through useEffect, we need to include the entire formState as a dependency for the effect to work correctly.
//We need to repeat formState, if we destruct properties form it
const { register, handleSubmit, formState,
formState: {
isDirty, dirtyFields, touchedFields, defaultValues, isLoading,
isSubmitted, isSubmitSuccessful, submitCount,
isValid, isValidating, validatingFields, errors, disabled
}
} = useForm({mode: "onblur"})
useEffect(() => {
/* Triggered formState property */
console.log( "On validation event", formState.validatingFields )
},[formState]);
<form onSubmit={handleSubmit(valori)}>
<p> {errors.terzo && "It is required"} </p>
<input {...register("primo", { required: true,
validate: (value)=>{
if(value.length > 5){
return "Failed triggered validate event"
}
}
})}/>
</form>
The useForm() watch method.
The useForm() watch method subscribes to changes in the specified registered input. It triggers a re-render whenever the input value is updated. It can include a defaultValue as its second argument, which sets the initial value of the watched input.
//The defaultValue watch will be updated with the useForm() defaultValue
const {register, handleSubmit, watch} = useForm({
defaultValues: { secondo: "seconded value" }
})
let seco = watch("secondo", "prime value")
let vedi = () => console.log( seco ) //"prime value" , "seconded value"
<form> <input {...register("secondo")}/> </form>
//We can create mutiple watch variables
let [sec, tez] = watch(["secondo", "terzo"]) //"secondo" , "terzo"
let ultimi = watch(["secondo", "terzo"]) //["secondo", "terzo"]
let tutto= watch() //{} containig the entire form.
In the useEffect() hook, we can monitor the form values without triggering a re-render by using the extracted unsubscribe method during the cleanup function.
//We destruct the callback function arguments
let sottoprimo = watch((value, { name, type }) => {
console.log( value )
})
useEffect(() => {
const { unsubscribe } = watch(({secondo}) => {
if( secondo ){
console.log(secondo)
}
})
return () => unsubscribe()
}, [watch])
For a detailed explanation of how and when watch
and useWatch
cause form components to re-render, refer to this page.
Last updated
Was this helpful?