- radio input default selection will return
nullinstead of empty string'' valueAsNumberwith empty input will returnNaNinstead of0
setValuewithout shouldUnregister:false will no longer deep clone its value instead with shallow clone
- new formState
isValidating, this will set totrueduring validation.
const {
formState: { isValidating },
} = useForm();- When invoking
reset({ value })value will be shallow clone value object which you have supplied instead of deepClone.
// ❌ avoid the following with deep nested default values
const defaultValues = { object: { deepNest: { file: new File() } } };
useForm({ defaultValues });
reset(defaultValues); // share the same reference
// ✅ it's safer with the following, as we only doing shallow clone with defaultValues
useForm({ deepNest: { file: new File() } });
reset({ deepNest: { file: new File() } });- New custom hook
useController: This custom hook is what powers Controller, and shares the same props and methods as Controller. It's useful to create reusable Controlled input, while Controller is the flexible option to drop into your page or form.
import React from 'react';
import { TextField } from '@material-ui/core';
import { useController } from 'react-hook-form';
function Input({ control, name }) {
const {
field: { ref, ...inputProps },
meta: { invalid, isTouched, isDirty },
} = useController({
name,
control,
rules: { required: true },
defaultValue: '',
});
return <TextField {...inputProps} inputRef={ref} />;
}useWatchwill retrieve the latest value fromreset(data)instead of returndefaultValue
useWatch({
name: 'test',
defaultValue: 'data', // this value will only show on the inital render
});- TS: name changed from
ValidationRulestoRegisterOptionsdue to valueAs functionality included asregisterfunction.
-
registerfunction with additional options to transform valuevalueAsDatevalueAsNumbersetValue
register({
valueAsNumber: true,
});
register({
valueAsNumber: true,
});
register({
setValueAs: (value) => value,
});defaultValuesis required to measureisDirty, keep a single source of truth to avoid multiple issues raised aroundisDirty- when
watchwithuseFieldArray,fieldsobject is no longer required as defaultValue
- watch('fieldArray', fields);
+ watch('fieldArray');Controllerwill have an extrarefprops to improve DX in terms of focus management.
<Controller
name="test"
render={(props) => {
return (
<input
value={props.value}
onChange={props.onChange}
ref={props.ref} // you can assign ref now without the use of `onFocus`
/>
);
}}
/>
// focus will work correct without the `onFocus` prop
<Controller name="test" as={<input />} />resolverwith group error object will no longer need withtriggerto show and clear error. This minor version made hook form look at parent error node to detect if there is any group error to show and hide.
const schema = z.object({
items: z.array(z.boolean()).refine((items) => items.some((item) => item)),
});
{
items.map((flag, index) => (
<input
type="checkbox"
defaultChecked={false}
// onChange={() => trigger("items")} now can be removed
ref={register}
name={`items.${index}`}
/>
));
}- with shouldUnregister set to false, empty Field Array will default [] as submission result.
const { handleSubmit } = useForm({
shouldUnregister: false,
});
useFieldArray({
name: 'test',
});
handleSubmit((data) => {
// shouldUnregister: false
// result: { data: {test: []} }
// shouldUnregister: true
// result: {}
});- when input unmounts
touchedanddirtyFieldswill no longer get removed fromformState(shouldUnregister: true).
- new formState
isSubmitSuccessfulto indicate successful submission setErrornow support focus on the actual input
setError('test', { message: 'This is required', shouldFocus: true });- with
shouldUnregister:falsedefaultValuesdata will be part of the submission data - with
shouldUnregister:falseconditional field is going to work withuseFieldArray setValuenow supportuseFieldArray
- setValue('test', 'data')
+ setValue('test', [{ test: '123' }]) // make it work for useFieldArray and target a field array key- remove
exactconfig at clearErrors
- clearErrors('test', { exact: false })
+ clearErrors('test') // does it automatically in the libclearErrorhave second option argument for clear errors which are exact or key name
register('test.firstName', { required: true });
register('test.lastName', { required: true });
clearErrors('test', { exact: false }); // will clear both errors from test.firstName and test.lastName
clearErrors('test.firstName'); // for clear single input error- all types from this lib has been exported. Important: only documented type: https://react-hook-form.com/ts will avoid breaking change.
errorsis also part offormStateobjectdisabledinput will not be part of the submission data by following the HTML standard
Controller'srenderprop will pass downnameprophandleSubmittake a second callback for errors callback- new mode
onTouchedwill only trigger validation after inputs are touched
registerno longer comparerefdifference with React Native
- IE 11 version will be required to install
@babel/runtime-corejs3as dependency at your own project
defaultValueis become required foruseFieldArrayat each input
- revert
getValueswill return default values before inputs registration
resolversupports both async and syncgetValueswill return default values before inputs registration
- export
ArrayFieldtype
- error message will support array of messages for specific type
- export type ValidateResult = Message | boolean | undefined;
+ export type ValidateResult = Message | Message[] | boolean | undefined;- Controller
onFocusworks with React Native - Controller stop producing
checkedprop by booleanvalue
- export
UseFormOptions,UseFieldArrayOptions,FieldError,FieldandModetype
- export
ValidationRulestype
- config for
shouldUnregisterwhich allow input to be persist even after unmount
useForm({
shouldUnregister: false, // unmount input state will be remained
});- auto focus with useFieldArray
append({}, (autoFocus = true));
prepend({}, (autoFocus = true));
insert({}, (autoFocus = true));- TS: NestedValue
import { useForm, NestedValue } from 'react-hook-form';
type FormValues = {
key1: string;
key2: number;
key3: NestedValue<{
key1: string;
key2: number;
}>;
key4: NestedValue<string[]>;
};
const { errors } = useForm<FormValues>();
errors?.key1?.message; // no type error
errors?.key2?.message; // no type error
errors?.key3?.message; // no type error
errors?.key4?.message; // no type erroruseWatch(new) subscribe to registered inputs.
<input name="test" ref={register} />;
function IsolateReRender() {
const { state } = useWatch({
name: 'test',
control,
defaultValue: 'default',
});
return <div>{state}</div>;
}getValues()support array of field names
getValues(['test', 'test1']); // { test: 'test', test1: 'test1' }useForm({ mode: 'all' })support all validation
-
rename
validationResolvertoresolver -
rename
validationContexttocontext -
rename
validateCriteriaModetocriteriaMode -
rename
triggerValidationtotrigger -
rename
clearErrortoclearErrors -
rename
FormContexttoFormProvider -
rename
dirtytoisDirty -
dirtyFieldschange type fromSettoObject -
Controller with render props API, and removed the following props:
- onChange
- onChangeName
- onBlur
- onBlurName
- valueName
-<Controller
- as={CustomInput}
- valueName="textValue"
- onChangeName="onTextChange"
- control={control}
- name="test"
-/>
+<Controller
+ render={({ onChange, onBlur, value }) => (
+ <CustomInput onTextChange={onChange} onBlur={onBlur} textValue={value} />
+ )}
+ control={control}
+ name="test"
+/>setErrorwill focus one error at a time and remove confusing set multiple errors, behavior change.- setError will persis an error if it's not part of the form, which requires manual remove with clearError
- setError error will be removed by validation rules, rules always take over errors
- setError('test', 'test', 'test')
+ setError('test', { type: 'test', message: 'bill'})setValuewill focus on input at a time
setValue('test', 'value', { shouldValidate: false, shouldDirty: false })- remove
validationSchemaand embrace validationresolver - remove
nestoption forwatch&getValues, so data return from both methods will be in FormValues shape.
-getValues({ nest: true }); // { test: { data: 'test' }}
-watch({ nest: true }); // { test: { data: 'test' }}
+getValues(); // { test: { data: 'test' }}
+watch(); // { test: { data: 'test' }}Controller: onChange will only evaluate payload as event like object. eg: react-select will no longer need the extraonChangemethod atController.
import { TextInput } from 'react-native';
-<Controller
- as={<TextInput style={{ borderWidth: 2, borderColor: 'black'}} />}
- name="text"
- control={args => ({
- value: args[0].nativeEvent.text,
- })}
- onChange={onChange}
-/>
+<Controller
+ as={<TextInput style={{ borderWidth: 2, borderColor: 'black'}} />}
+ name="text"
+ control={args => args[0].nativeEvent.text}
+ onChange={onChange}
+/>- improve module exports:
import { useForm } from 'react-hook-form';- nested
errorsobject and better typescript support
type form = {
yourDetail: {
firstName: string;
};
};
errors?.yourDetail?.firstName;- triggerValidation argument change from
Object,Object[]toString,String[]
triggerValidation('firstName');
triggerValidation(['firstName', 'lastName']);- watch support
{ nest: boolean }
watch(); // { 'test.firstName': 'bill' }
watch({ nest: true }); // { test: { firstName: 'bill' } }- improve custom
register
register('test', { required: true });- setError` support nested object
setError('yourDetail.firstName', 'test');
errors.yourDetails.firstName;handleSubmitno longer rerun array inputs containsundefinedornull
- move
RHFInputinto the main repo and rename it toController
<Controller control={control} name="test" />-
validationSchemaOption: hardly anyone want to use validation with abort early, or change the config. -
native validation: hardly anyone used this feature. https://react-hook-form.com/api/#Browserbuiltinvalidation
React Hook Form return a new formState: Object which contain the following information
dirty: when user interactive any fieldstouched: what are the fields have interactedisSubmitted: whether the form have been triggered with submitting
const {
formState: { dirty, touched, isSubmitted },
} = useForm();- support
ref={register}instead of onlyref={register()}