Skip to content

Migration Guide

V6 to V7

Hi there,

React Hook Form focus on the following aspect on Version 7:

  • (DX) Strict typed form

  • Reduce package size

  • Performance enhancements

  • improve API's simplicity and consistency

Here are the changes that you would need to adjust. We sincerely hope those changes aren't creating too much trouble for your codebase. If you are wondering some of the rationals behind and discussion that we had among the community, you can take a look at this RFC for more details.

❤️ React hook Form Team

Upgrade Packages

Important: if you are using TypeScript, react hook form will need to have TS 4.1 above.

npm i react-hook-form@latest // react-hook-form: "^7.0.0"
npm i @hookform/resolvers@latest // @hookform/resolvers: "^2.0.0" if you are using resolvers

npm i @hookform/devtools@latest  // @hookform/devtools: "^3.0.0" if you are using devtools


register method is no longer occurred at ref, instead invoke the function itself and spread the props into the input. The function itself will return the following props: onChange, onBlur, name and ref.

Important: input value and reference will no longer get removed after unmount.

- <input ref={register({ required: true })} name="test" />
+ <input {...register('name', { required: true })} /> 
+ <TextInput {...register('name', { required: true })} />

You can use this codemod library to speed up the process: by running the following command:

npx @hookform/codemod v7/update-register

On top of that, for better type support, we have removed bracket syntax and replaced with dot syntax.

- test[2].test
+ test.2.test

Custom register

You will no longer need the name attribute for custom register, you can supply the name of the input straight way.

- register({ name: 'test' })
+ register('test') 


valueAs will be invoked before validate function.

- <input ref={register({ valueAsNumber: true, validate: (value) => parseInt(value) === 2 ) })} name="test" />
+ <input {...register('test', { valueAsNumber: true, validate: (value) => value === 2 ) }} /> // no longer need to parse again

Missing ref

register works for any uncontrolled inputs , however, there are components which expose ref name differently.

const { ref, } = register('test') // invoke this before render
<Input {} inputRef={ref} />


We made some change to align consistently with useController's API.

  • as prop has been removed, and we will consistently using render prop in v7.

  • render prop will return an object which contains field and fieldState.

- <Controller as={<input />} />
+ <Controller render={({ field }) => <input {...field} />}

- <Controller render={(props, meta) => <input {...props} />} />
+ <Controller render={({ field, fieldState }) => <input {...field} />} />


In V7, we made some changes in our API to keep them more consistent and declarative. Reset's second option is the exact reason.

- reset(values, { isDirty: true })
+ // second argument is still optional
+ reset(values, { 
+   keepDefaultValues: true, // new
+   keepValues: true, // new
+   keepDirty: true, 
+ })


errors object has been moved into formState object. This will info hook form that errors object is been subscribed.

You can use this codemod library to speed up the process: by running the following command:

npx @hookform/codemod v7/move-errors-to-formState
- const { errors } = useForm();
+ const { formState: { errors } } = useForm();


watch an array of inputs will return array instead object

- const { test, test1 } = watch(['test', 'test1']);
+ const [test, test1] = watch(['test', 'test1']);


Manually triggers form or input validation. This method is also useful when you have depedant validation as react hook form

- await trigger("test") // Returns true|false
+ await trigger("test") // Returns void


We have fixed the setError function to be consistent with the rest of the APIs.

- setError('test', { type: 'type', message: 'issue', shouldFocus: true })
+ setError('test', { type: 'type', message: 'issue' }, { shouldFocus: true })


Renamed touched to touchedFields.

- const { touched } = formState;
+ const { touchedFields } = formState;


We made some huge improvement on resolver, and the third argument now need to host more value than just a single boolean.

- 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 


We are offering better focus management in useFieldArray, so if you want to disable the focus behaviour, you will have to adjust the option as well.

- append({ test: 'test' }, false);
+ append({ test: 'test' }, { shouldFocus: false });

useFieldArray no longer need to supply with a generic, formValues type will inherited from useForm.

- useFieldArray<FieldArrayValues>();
+ useFieldArray();

Input name will be strictly type checked with as const.

<input {...register(`test.${index}.firstName` as const)} />


This component is getting deprecated and using useController instead.

- <TypedController
-   as="textarea"
-   name={['nested', 'object', 'test']}
-   defaultValue=""
-   rules={{ required: true }}
- />
+ <Controller
+   name={'nested.object.test'}
+   defaultValue=""
+   rules={{ required: true }}
+   render={({ field }) => <textarea {...field} />}
+ />);