- new:
reset
optional prop:keepDirtyValues
reset(
{
firstName: 'bill', // if firstName is dirty then the value will be retained
lastName: 'luo',
},
{ keepDirtyValues: true }, // keep any changed field
);
useFieldArray
auto-correct field array errors on user action
const { append } = useFieldArray();
append({ data: '' }); // will auto correct existing field array errors if any
- improve checkboxes value determine by defaultValues
useForm({
defaultValues: {
checkboxes: [], // register checkbox will be determine as array of checkboxes
},
});
register('checkboxes'); // will return array as value
- tsconfig config change from es2017 to es2018
register
API optionsdeps
now support string
register('test', { deps: 'test' });
- new option for
setFocus
to select the entire field value
setFocus('fieldName', { shouldSelect: true });
onTouched
mode will honorfocusout
event
getFieldState
get individual field state
export default function App() {
const {
register,
getFieldState,
formState: { isDirty, isValid },
} = useForm({
mode: 'onChange',
defaultValues: {
firstName: '',
},
});
// you can invoke before render or within the render function
const fieldState = getFieldState('firstName');
return (
<form>
<input {...register('firstName', { required: true })} />
<p>{getFieldState('firstName').isDirty && 'dirty'}</p>
<p>{getFieldState('firstName').isTouched && 'touched'}</p>
<button
type="button"
onClick={() => console.log(getFieldState('firstName'))}
>
field state
</button>
</form>
);
}
useController
return prop:onChange
,onBlur
andref
will be memorized withuseCallback
useFieldArray
changekeyName
is no longer required when field value containsid
const App = () => {
const { control, register, handleSubmit } = useForm<FormValues>({
defaultValues: {
test: [{ id: 'UUID5678', test: 'data' }], // id value will be retained
},
});
const { fields, append } = useFieldArray({
control,
name: 'test',
});
return (
<form>
{fields.map((field, index) => {
return <input key={field.id} {...register(`test.${index}.test`)} />;
})}
<button
type={'button'}
onClick={() => {
append({
id: 'UUID1234', // id value will be retained
test: '1234',
});
}}
>
append
</button>
</form>
);
};
useFormState
will no longer fire state update after hook unmountUseFormHandleSubmit
type will infer formValues
- Browser native reset API will no longer be invoked when
reset
provided with value
const onSubmit = (data) => {};
React.useEffect(() => {
if (formState.isSubmitSuccessful) {
reset({ something: '' });
}
}, [formState, reset]);
handleSubmit(onSubmit);
to
const onSubmit = (data) => {
setSubmittedData(data);
reset(data); // no longer need to have useEffect
};
handleSubmit(onSubmit);
shouldUseNativeValidation
will pass down validation props both at client and server render
const { register } = useForm()
<input {...register('name', { required: true })} />
<input name="name" required /> // both client and server render
- register
onChange
will share same logic withuseController
for non standard event payload
const { onChange } = register('test');
onChange('stringIsValid'); // this is only valid use case for JS
- empty node in
formState
will no longer gets unset
- new
exact
prop foruseWatch
- new
exact
prop foruseFormState
useWatch({
name: 'test.test',
exact: true,
});
useFormState({
name: 'test.test',
exact: true,
});
useWatch
subscription will occurred atuseEffect
instead beforerender
- new
resetField
API
const { resetField } = useForm();
resetField('test');
resetField('test', {
keepError: true,
keepDirty: true,
keepTouched: true,
defaultValue: 'test1',
});
useController
will return shallow clone value for the following data type on each rerender- object:
{... value}
- array:
[...value]
- object:
- revert
FieldPathWithValue
- bring back
FieldPathWithValue
- schema errors parent object look up
const validationSchema = object().shape({
questions: array().min(1, 'Array cannot be empty'),
});
// the above schema will be picked up by field array action
// the logic applies to group error object eg checkboxes
<button
type="button"
onClick={() => {
remove(questionIndex);
}}
>
Remove
</button>;
- new type
FieldPathWithValue
to improve generic components type support
type ExpectedType = { test: string };
const Generic = <FormValues extends FieldValues>({
name,
control,
}: {
name: FieldPathWithValue<FormValues, ExpectedType>;
control: Control<FormValues>;
}) => {
const {
field: { value, ...fieldProps },
} = useController<FormValues, ExpectedType>({
name,
control,
defaultValue: { test: 'value' },
});
return <input type="text" value={value.test} {...fieldProps} />;
};
formState
subscription no longer subscribed atuseEffect
instead the execution of each hook
register
allowed pass customonChange
andonBlur
<input
type="text"
{...register('test', {
onChange: (e) => {},
onBlur: (e) => {},
})}
/>
useFieldArray
new methodreplace()
const { control } = useForm({
defaultValues: {
test: [{ value: 'lorem' }, { value: 'ipsum' }],
},
});
const { fields, replace } = useFieldArray({
control,
name: 'test',
});
const handleFullyReplacement = (): void => {
// remove old and set fully new values
replace([{ value: 'dolor' }, { value: 'sit' }, { value: 'amet' }]);
};
- Improved to not map types defined with
interface
.
import { useForm } from 'react-hook-form';
interface CustomValue {
custom: string;
}
type FormValues = {
fieldName: CustomValue;
};
const { formState: errors } = useForm<FormValues>({
defaultValues: { fieldName: { custom: 'value' } },
});
register
add dependent validation
const App = () => {
const { register, getValues } = useForm();
return (
<form>
<input
{...register('firstName', {
validate: (value) => {
return getValues('lastName') === value;
},
})}
/>
<input {...register('lastName', { deps: ['firstName'] })} /> // dependant validation
</form>
);
};
Trigger
- Trigger will enable object name trigger and field array name trigger
useFieldArray({ name: 'test' });
trigger('name'); // will trigger the whole field array to validate
register
- added a
disabled
prop as an option to toggle input disable attribute - register will be able to seek input DOM reference through the
ref
callback
register('test', { disabled: true }) // will set value to undefined and pass disabled down to the input attribute
<div {...register('test')}>
<input name="test" /> // this input will be registered
</div>
useWatch
- added
disabled
prop to toggle the state subscription.
useWatch({ disabled: true }); // you toggle the subscription
useFormState
- added
disabled
prop to toggle the state subscription.
useFormState({ disabled: true }); // you toggle the subscription
setValue
- allow set value for non-registered inputs include nested object and array field.
<input {...register('test.0.firstName')} />
setValue('test', [{ firstName: 'bill' }, {firstName: 'kotaro}, {firstName: 'joris'}]) // this will works
- new
useForm
configdelayError
useForm({
delayError: 500, // delay error appear with 500ms
});
update
method to update an field array inputs
const { update } = useFieldArray();
update(0, data); // update an individual field array node
defaultValue
is no longer a required prop for register input withuseFieldArray
- new config at
useForm
to enabled native browser validation
const { register, handleSubmit } = useForm({
shouldUseNativeValidation: true,
});
useController
no longer access inputref
exceptfocus
event for focus management
setValue
supportshouldTouch
to update formState touchFields
setValue('firstName', 'value', { shouldTouch: true });
register
now acceptvalue
as option
register('firstName', { value: 'value' });
isValid
will initialise asfalse
shouldUnregister: false
should not shallow merge or register absent input fields fromdefaultValues
trigger
support focus with error input
trigger('inputName', { shouldFocus: true });
handleSubmit
willthrow
error within the onSubmit callback
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 initial render
});
- TS: name changed from
ValidationRules
toRegisterOptions
due to valueAs functionality included asregister
function.
-
register
function with additional options to transform valuevalueAsDate
valueAsNumber
setValueAs
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()}