useForm
willregister
missing inputs fromdefaultValues
const App = () => {
const { register, handleSubmit } = useForm({
defaultValues: {
test: { firstName: 'bill', lastName: 'luo' },
},
});
const onSubmit = (data) => {
// missing registered input will be included
console.log(data); // { test: { firstName: 'bill', lastName: 'luo' } }
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('test.firstName')} />
<button />
</form>
);
};
isSubmitSuccessful
will return false whenhandleSubmit
callback failed withError
orPromise
reject.- unmounted input will no longer get validated even with
shouldUnregister: false
- new
name
prop foruseFormState
to subscribe to individual inputs.
useFormState({
name: 'inputName', // optional and can be array of inputs' name as well
});
- set
shouldUnregister
totrue
will not shallow mergedefaultValues
shouldUnregister
config to remove input value after unmount
// Global config (can't be overwrite)
useForm({
shouldUnregister: true // default to false
})
// Component/Hook level config (can not overwrites global config)
register('test', {
shouldUnregister: true // default to false
})
<Controller shouldUnregister={true} />
useController({ shouldUnregister: true })
useFieldArray({ shouldUnregister: true })
register
will retrieveonChange
's target value when component'ref is not a valid input element.
- change type name from
RefCallbackHandler
toUseFormRegisterReturn
for register callback's return
useFieldArray
will produce an empty array[]
when no field is presented.
setValue
with field array willregister
all inputs before rendering.
append
,prepend
andinsert
willregister
deeply nested inputs atuseFieldArray
.
- typescript array index restriction removed.
append
,prepend
andinsert
willregister
inputs during each action atuseFieldArray
.
- change
ArrayKey
type tonumber | '${number}'
- Change
useController
'smeta
intofieldState
and includeformState
, these change will be applied toController
too.
- const { field, meta } = useController({ control });
+ const { field, fieldState, formState } = useController({ control });
- typescript array index support is changed to
49
instead of99
- Breaking change:
valueAs
will be run before the built-in validation andresolver
- <input {...register('test', { validate: (data: string) => {}, valueAsNumber: true })} />
+ <input {...register('test', { validate: (data: number) => {}, valueAsNumber: true })} />
useWatch
will no longer requireddefaultValue
for field Array
- Breaking change: shallow merge defaultValues with result (#4074)
useForm({ defaultValues: { test: 'test' } });
getValues(); // v6 will return {}
getValues(); // v7 will return { test: 'test' }
- Breaking change:
setError
'sshouldFocus
option has been moved into the third argument.
- setError('test', { type: 'type', message: 'issue', shouldFocus: true })
+ setError('test', { type: 'type', message: 'issue' }, { shouldFocus: true })
- Breaking change: type name changes:
- UseFormMethods
+ UseFormReturn
- UseFormOptions
+ UseFormProps
- UseFieldArrayMethods
+ UseFieldArrayReturn
- UseFieldArrayOptions
+ UseFieldArrayProps
- UseControllerMethods
+ UseControllerReturn
- UseControllerOptions
+ UseControllerProps
- ArrayField
+ FieldArray
- fix
setValue
withController
andreset
withuseFieldArray
issues: 4111 & 4108 (#4113)
- Breaking change:
setError
'sshouldFocus
option has been moved to the third argument.
- setError('test', { type: 'type', message: 'issue', shouldFocus: true })
+ setError('test', { type: 'type', message: 'issue' }, { shouldFocus: true })
- fix #4078 issue with watch + mode: onChange
- remove internal deep clone (#4088)
- remove transformToNestObject (#4089)
-
field name reference will be removed with
unregister
(#4010) -
Breaking change: improve field array remove result and no longer remove field array value after unmount
const { remove } = useFieldArray({ name: 'test' })
remove();
getValues(); // V6: result form value {}
getValues(); // V7: result form value { test: [] }
- change internal field names into
Set
(#4015) - improve
onChange
perf with `resolver (#4017) - improve field array name look up perf (#4030)
- new custom hook
useFormState
(#3740)
const { isDirty, errors } = useFormState();
watch
support can subscribe to the entire form with a callback
watch((data, { name, type }) => {
console.log('formValue', data);
console.log('name', name);
console.log('type', type);
});
useController
includes newisValidating
state (#3778)useController
includes newerror
state (#3921)
const {
meta: { error, isValidating },
} = useController({ name: 'test' });
- new
unregister
second argument (#3964)
unregister('test', { keepDirty: true });
- Resolver add
field
being validated (#3881)
- resolver: (values: any, context?: object) => Promise<ResolverResult> | ResolverResult
+ resolver: (
+ values: any,
+ context?: object,
+ options: {
+ criteriaMode?: 'firstError' | 'all',
+ names?: string[],
+ fields: { [name]: field } // Support nested field
+ }
+ ) => Promise<ResolverResult> | ResolverResult
useFieldArray
action can focus input by name and index
append(object, config: { shouldDirty: boolean, focusIndex: number, focusName: string })
insert(object, config: { shouldDirty: boolean, focusIndex: number, focusName: string })
prepend(object, config: { shouldDirty: boolean, focusIndex: number, focusName: string })
-
Breaking change: No longer support IE 11 support
-
Breaking change:
register
has been changed from register atref
to a function which needs to be spread as props.
- <input ref={register, { required: true }} name="test" />
+ <input {...register('name', { required: true })} />
+ <TextInput {...register('name', { required: true })} />
- Breaking change:
name
with array will only support dot syntax instead of brackets.
- test[2].test
+ test.2.test
- Breaking change: remove
as
prop atController
and fix render prop consistency (#3732)
- <Controller render={props => <input {...props} />} />
+ <Controller render={({ field }) => <input {...field} />} />
- Breaking change: remove
errors
alias (#3737)
- const { errors } = useForm();
+ const { formState: { errors } } = useForm();
- Breaking change: improved
reset
second argument (#3905)
- reset({}, { isDirty: true })
+ reset({}, { keepIsDirty: true })
- Breaking change: change
touched
totouchedFields
for consistency (#3923)
- const { formState: { touched } } = useForm();
+ const { formState: { touchedFields }} = useForm();
- Breaking change:
trigger
will no longer return validation result.
- await trigger('test') // return true or false
+ trigger('test') // void
-
remove
isSubmitting
proxy (#4000) -
input
register
will no longer be removed due to unmount, user will have to manually invokeunregister
useWatch
internal mechanism improvement (#3754)Controller
anduseController
applyuseFormState
internally and improve performance (#3778)register
type support for input name (#3738)Controller
anduseCOntroller
type support for input name (#3738)useFieldArray
internal logic and data structure improvement (#3858)- improve
useFieldArray
internal fields update with subscription (#3943) - improve tests structure (#3916)
useWatch
type improvement (#3931)- improve type support for nested field array with
const
(#3920) - improve
useFieldArray
internal type structure (#3986) MutationObserver
removed fromuseForm
- radio input default selection will return
null
instead of empty string''
valueAsNumber
with empty input will returnNaN
instead of0
setValue
without shouldUnregister:false will no longer deep clone its value instead with shallow clone
- new formState
isValidating
, this will set totrue
during 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} />;
}
useWatch
will 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
ValidationRules
toRegisterOptions
due to valueAs functionality included asregister
function.
-
register
function with additional options to transform valuevalueAsDate
valueAsNumber
setValue
register({
valueAsNumber: true,
});
register({
valueAsNumber: true,
});
register({
setValueAs: (value) => value,
});
defaultValues
is required to measureisDirty
, keep a single source of truth to avoid multiple issues raised aroundisDirty
- when
watch
withuseFieldArray
,fields
object is no longer required as defaultValue
- watch('fieldArray', fields);
+ watch('fieldArray');
Controller
will have an extraref
props 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 />} />
resolver
with group error object will no longer need withtrigger
to 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
touched
anddirtyFields
will no longer get removed fromformState
(shouldUnregister: true).
- new formState
isSubmitSuccessful
to indicate successful submission setError
now support focus on the actual input
setError('test', { message: 'This is required', shouldFocus: true });
- with
shouldUnregister:false
defaultValues
data will be part of the submission data - with
shouldUnregister:false
conditional field is going to work withuseFieldArray
setValue
now supportuseFieldArray
- setValue('test', 'data')
+ setValue('test', [{ test: '123' }]) // make it work for useFieldArray and target a field array key
- remove
exact
config at clearErrors
- clearErrors('test', { exact: false })
+ clearErrors('test') // does it automatically in the lib
clearError
have 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.
errors
is also part offormState
objectdisabled
input will not be part of the submission data by following the HTML standard
Controller
'srender
prop will pass downname
prophandleSubmit
take a second callback for errors callback- new mode
onTouched
will only trigger validation after inputs are touched
register
no longer compareref
difference with React Native
- IE 11 version will be required to install
@babel/runtime-corejs3
as dependency at your own project
defaultValue
is become required foruseFieldArray
at each input
- revert
getValues
will return default values before inputs registration
resolver
supports both async and syncgetValues
will return default values before inputs registration
- export
ArrayField
type
- error message will support array of messages for specific type
- export type ValidateResult = Message | boolean | undefined;
+ export type ValidateResult = Message | Message[] | boolean | undefined;
- Controller
onFocus
works with React Native - Controller stop producing
checked
prop by booleanvalue
- export
UseFormOptions
,UseFieldArrayOptions
,FieldError
,Field
andMode
type
- export
ValidationRules
type
- config for
shouldUnregister
which 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 error
useWatch
(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
validationResolver
toresolver
-
rename
validationContext
tocontext
-
rename
validateCriteriaMode
tocriteriaMode
-
rename
triggerValidation
totrigger
-
rename
clearError
toclearErrors
-
rename
FormContext
toFormProvider
-
rename
dirty
toisDirty
-
dirtyFields
change type fromSet
toObject
-
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"
+/>
setError
will 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'})
setValue
will focus on input at a time
setValue('test', 'value', { shouldValidate: false, shouldDirty: false })
- remove
validationSchema
and embrace validationresolver
- remove
nest
option 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 extraonChange
method 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
errors
object 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;
handleSubmit
no longer rerun array inputs containsundefined
ornull
- move
RHFInput
into 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()}