Important: TypeScript 4.1 or above is required to work with React Hook Form — several exported types (recursive Path<T> field-name inference, template-literal field paths) rely on generics features introduced in that release, and older versions may not type-check correctly. React Hook Form itself is developed and tested against TypeScript 6.x.
Many of the types below share two generic parameters beyond the form values type (TFieldValues):
TContext— the type of the second argument passed touseForm'scontextoption. It's forwarded, unchanged, to yourresolverso validation logic can read values that live outside the form (e.g. a logged-in user, feature flags).TTransformedValues— the shape of the data yourresolverproduces after transforming it (e.g. a Zod schema with.transform()that turns a string into aDate). It defaults toTFieldValuesand only differs when a resolver reshapes data before it reacheshandleSubmit'sonValidcallback.
</> Resolver
A Resolver lets you plug in an external validation library (such as Zod, Yup, or Joi) in place of React Hook Form's built-in validation. It receives the current form values and must return { values, errors }.
import { useForm, Resolver } from "react-hook-form"type FormValues = {firstName: stringlastName: string}const resolver: Resolver<FormValues> = async (values) => {return {values: values.firstName ? values : {},errors: !values.firstName? {firstName: {type: "required",message: "This is required.",},}: {},}}export default function App() {const {register,handleSubmit,formState: { errors },} = useForm<FormValues>({ resolver })const onSubmit = handleSubmit((data) => console.log(data))return (<form onSubmit={onSubmit}><input {...register("firstName")} placeholder="Bill" />{errors?.firstName && <p>{errors.firstName.message}</p>}<input {...register("lastName")} placeholder="Luo" /><input type="submit" /></form>)}
</> SubmitHandler
import { useForm, SubmitHandler } from "react-hook-form"type FormValues = {firstName: stringlastName: stringemail: string}export default function App() {const { register, handleSubmit } = useForm<FormValues>()const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><input {...register("firstName")} /><input {...register("lastName")} /><input type="email" {...register("email")} /><input type="submit" /></form>)}
</> SubmitErrorHandler
import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form"type FormValues = {firstName: stringlastName: stringemail: string}export default function App() {const { register, handleSubmit } = useForm<FormValues>()const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data)const onError: SubmitErrorHandler<FormValues> = (errors) =>console.log(errors)return (<form onSubmit={handleSubmit(onSubmit, onError)}><input {...register("firstName", { required: true })} /><input {...register("lastName", { minLength: 2 })} /><input type="email" {...register("email")} /><input type="submit" /></form>)}
</> FormSubmitHandler
The type for the Form component's onSubmit prop — unlike SubmitHandler, the payload includes the raw FormData and formDataJson from the submission.
export type FormSubmitHandler<TTransformedValues> = (payload: {data: TTransformedValuesevent?: React.BaseSyntheticEventformData: FormDataformDataJson: stringmethod?: "post" | "put" | "delete"}) => unknown | Promise<unknown>
</> Control
import { useForm, useWatch, Control } from "react-hook-form"type FormValues = {firstName: stringlastName: string}function IsolateReRender({ control }: { control: Control<FormValues> }) {const firstName = useWatch({control,name: "firstName",defaultValue: "default",})return <div>{firstName}</div>}export default function App() {const { register, control, handleSubmit } = useForm<FormValues>()const onSubmit = handleSubmit((data) => console.log(data))return (<form onSubmit={onSubmit}><input {...register("firstName")} /><input {...register("lastName")} /><IsolateReRender control={control} /><input type="submit" /></form>)}
</> UseFormReturn
export type UseFormReturn<TFieldValues extends FieldValues = FieldValues,TContext = any,TTransformedValues = TFieldValues,> = {watch: UseFormWatch<TFieldValues>getValues: UseFormGetValues<TFieldValues>getFieldState: UseFormGetFieldState<TFieldValues>setError: UseFormSetError<TFieldValues>clearErrors: UseFormClearErrors<TFieldValues>setValue: UseFormSetValue<TFieldValues>setValues: UseFormSetValues<TFieldValues>trigger: UseFormTrigger<TFieldValues>formState: FormState<TFieldValues>resetField: UseFormResetField<TFieldValues>reset: UseFormReset<TFieldValues>resetDefaultValues: UseFormResetDefaultValues<TFieldValues>handleSubmit: UseFormHandleSubmit<TFieldValues, TTransformedValues>unregister: UseFormUnregister<TFieldValues>control: Control<TFieldValues, TContext, TTransformedValues>register: UseFormRegister<TFieldValues>setFocus: UseFormSetFocus<TFieldValues>subscribe: UseFormSubscribe<TFieldValues>}
</> UseFormProps
export type UseFormProps<TFieldValues extends FieldValues = FieldValues,TContext = any,TTransformedValues = TFieldValues,> = Partial<{mode: Modedisabled: booleanreValidateMode: Exclude<Mode, "onTouched" | "all">defaultValues: DefaultValues<TFieldValues> | AsyncDefaultValues<TFieldValues>values: TFieldValueserrors: FieldErrors<TFieldValues>resetOptions: Parameters<UseFormReset<TFieldValues>>[1]resolver: Resolver<TFieldValues, TContext, TTransformedValues>context: TContextshouldFocusError: booleanshouldUnregister: booleanshouldUseNativeValidation: booleanprogressive: booleancriteriaMode: CriteriaModedelayError: numberformControl?: Omit<UseFormReturn<TFieldValues, TContext, TTransformedValues>,"formState">validate: ValidateForm<TFieldValues>}>
</> ValidateForm
The type of useForm's validate option — form-level validation that runs alongside (or instead of) a resolver.
export type ValidateFormEventType =| "blur"| "focusout"| "change"| "submit"| "trigger"| "valid"export type ValidateForm<TFormValues extends FieldValues> = (props: {formValues: TFormValuesformState: FormState<TFormValues>eventType?: ValidateFormEventTypename?: FieldPath<TFormValues> | FieldPath<TFormValues>[]}) => FormValidateResult<TFormValues> | Promise<FormValidateResult<TFormValues>>export type FormValidateResult<TFormValues> =| Partial<Record<keyof TFormValues,{ message: string | string[] | boolean | undefined; type: string }>>| string| boolean
Return true for a valid form. Returning a string sets formState.errors.form; returning an object keyed by field name sets formState.errors.form.<fieldName> for each entry.
</> UseFormRegister
The type of register.
export type UseFormRegister<TFieldValues extends FieldValues> = <TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,>(name: TFieldName,options?: RegisterOptions<TFieldValues, TFieldName>) => UseFormRegisterReturn<TFieldName>
</> UseFormRegisterReturn
The shape returned by invoking register. Useful for typing a custom Input component that spreads {...register(name)}.
export type UseFormRegisterReturn<TFieldName extends InternalFieldName = InternalFieldName,> = {onChange: ChangeHandleronBlur: ChangeHandlerref: RefCallBackname: TFieldNamemin?: string | numbermax?: string | numbermaxLength?: numberminLength?: numberpattern?: stringrequired?: booleandisabled?: boolean}
</> UseFormUnregister
The type of unregister.
export type UseFormUnregister<TFieldValues extends FieldValues> = (name?:| FieldPath<TFieldValues>| FieldPath<TFieldValues>[]| readonly FieldPath<TFieldValues>[],options?: Omit<KeepStateOptions,| "keepIsSubmitted"| "keepSubmitCount"| "keepValues"| "keepDefaultValues"| "keepErrors"> & { keepValue?: boolean; keepDefaultValue?: boolean; keepError?: boolean }) => void
</> UseFormWatch
The type of watch. Overloaded — called with no argument, a single field name, an array of field names, or a callback for subscribing without triggering a re-render.
export type UseFormWatch<TFieldValues extends FieldValues> = {(): TFieldValues<TFieldNames extends readonly FieldPath<TFieldValues>[]>(names: readonly [...TFieldNames],defaultValue?: DeepPartial<TFieldValues>): FieldPathValues<TFieldValues, TFieldNames><TFieldName extends FieldPath<TFieldValues>>(name: TFieldName,defaultValue?: FieldPathValue<TFieldValues, TFieldName>): FieldPathValue<TFieldValues, TFieldName>(callback: WatchObserver<TFieldValues>,defaultValues?: DeepPartial<TFieldValues>): Subscription}
</> UseFormSubscribe
The type of subscribe — subscribe to formState changes without triggering a component re-render.
export type UseFormSubscribe<TFieldValues extends FieldValues> = <TFieldNames extends readonly FieldPath<TFieldValues>[],>(payload: {name?: readonly [...TFieldNames] | TFieldNames[number]formState?: Partial<ReadFormState>callback: (data: Partial<FormState<TFieldValues>> & {values: TFieldValuesname?: InternalFieldNametype?: EventType}) => voidexact?: boolean}) => () => void
</> UseFormHandleSubmit
The type of the handleSubmit function.
export type UseFormHandleSubmit<TFieldValues extends FieldValues,TTransformedValues = TFieldValues,> = (onValid: SubmitHandler<TTransformedValues>,onInvalid?: SubmitErrorHandler<TFieldValues>) => (e?: React.BaseSyntheticEvent) => Promise<void>
</> UseFormReset
The type of the reset function.
export type UseFormReset<TFieldValues extends FieldValues> = (values?:| DefaultValues<TFieldValues>| TFieldValues| ResetAction<TFieldValues>,keepStateOptions?: KeepStateOptions) => void
</> UseFormResetField
The type of the resetField function.
export type UseFormResetField<TFieldValues extends FieldValues> = <TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,>(name: TFieldName,options?: ResetFieldConfig<TFieldValues, TFieldName>) => void
</> UseFormResetDefaultValues
The type of the resetDefaultValues function.
export type UseFormResetDefaultValues<TFieldValues extends FieldValues> = (values: DefaultValues<TFieldValues> | TFieldValues,options?: Partial<Pick<KeepStateOptions, "keepDirty" | "keepIsValid">>) => void
</> UseFormSetError
The type of the setError function.
export type UseFormSetError<TFieldValues extends FieldValues> = (name:| FieldPath<TFieldValues>| `root.${string}`| "root"| "form"| `form.${string}`,error: ErrorOption,options?: {shouldFocus: boolean}) => void
</> UseFormClearErrors
The type of the clearErrors function.
export type UseFormClearErrors<TFieldValues extends FieldValues> = (name?:| FieldPath<TFieldValues>| FieldPath<TFieldValues>[]| readonly FieldPath<TFieldValues>[]| `root.${string}`| "root"| "form"| `form.${string}`) => void
</> UseFormSetValue
The type of the setValue function.
export type UseFormSetValue<TFieldValues extends FieldValues> = <TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,>(name: TFieldName,value: FieldPathValue<TFieldValues, TFieldName>,options?: SetValueConfig) => void
</> UseFormSetValues
The type of the setValues function.
export type UseFormSetValues<TFieldValues extends FieldValues> = (value: Partial<TFieldValues> | ((formValues: TFieldValues) => TFieldValues),options?: SetValueConfig) => void
</> UseFormSetFocus
The type of the setFocus function.
export type SetFocusOptions = Partial<{shouldSelect: boolean}>export type UseFormSetFocus<TFieldValues extends FieldValues> = <TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,>(name: TFieldName,options?: SetFocusOptions) => void
</> UseFormGetValues
The type of the getValues function. This type is overloaded; it can be called with no arguments, a single field name, or an array of field names.
export type GetValuesConfig = {dirtyFields?: booleantouchedFields?: boolean}export type UseFormGetValues<TFieldValues extends FieldValues> = {(name?: undefined, config?: GetValuesConfig): TFieldValues<TFieldName extends FieldPath<TFieldValues>>(name: TFieldName,config?: GetValuesConfig): FieldPathValue<TFieldValues, TFieldName><TFieldNames extends FieldPath<TFieldValues>[]>(names: readonly [...TFieldNames],config?: GetValuesConfig): [...FieldPathValues<TFieldValues, TFieldNames>]}
</> UseFormGetFieldState
The type of the getFieldState function.
export type UseFormGetFieldState<TFieldValues extends FieldValues> = <TFieldName extends FieldPath<TFieldValues>,>(name: TFieldName,formState?: FormState<TFieldValues>) => {invalid: booleanisDirty: booleanisTouched: booleanisValidating: booleanerror?: FieldError}
</> UseFormTrigger
The type of the trigger function.
export type TriggerConfig = Partial<{shouldFocus: boolean}>export type UseFormTrigger<TFieldValues extends FieldValues> = (name?:| FieldPath<TFieldValues>| FieldPath<TFieldValues>[]| readonly FieldPath<TFieldValues>[],options?: TriggerConfig) => Promise<boolean>
</> UseWatchProps
The props accepted by the useWatch hook.
export type UseWatchProps<TFieldValues extends FieldValues = FieldValues> = {defaultValue?: unknowndisabled?: booleanname?:| FieldPath<TFieldValues>| FieldPath<TFieldValues>[]| readonly FieldPath<TFieldValues>[]control?: Control<TFieldValues>exact?: booleancompute?: (formValues: TFieldValues) => TFieldValues}
</> WatchProps
The props for the Watch render-prop component.
export type WatchProps<TFieldName extends WatchName<TFieldValues>,TFieldValues extends FieldValues = FieldValues,TContext = any,TTransformedValues = TFieldValues,TComputeValue = undefined,> = {control?: Control<TFieldValues, TContext, TTransformedValues>name?: TFieldNamedisabled?: booleanexact?: booleandefaultValue?: WatchDefaultValue<TFieldName, TFieldValues>compute?: (value: WatchValue<TFieldName, TFieldValues>) => TComputeValuerender: (value: WatchRenderValue<TFieldName, TFieldValues, TComputeValue>) => React.ReactNode | React.ReactNode[]}
</> UseFormStateProps
The props accepted by the useFormState hook.
export type UseFormStateProps<TFieldValues extends FieldValues,TTransformedValues = TFieldValues,> = Partial<{control?: Control<TFieldValues, any, TTransformedValues>disabled?: booleanname?:| FieldPath<TFieldValues>| FieldPath<TFieldValues>[]| readonly FieldPath<TFieldValues>[]exact?: boolean}>
</> FormStateProps
The props for the FormState render-prop component.
export type FormStateProps<TFieldValues extends FieldValues,TTransformedValues = TFieldValues,> = UseFormStateProps<TFieldValues, TTransformedValues> & {render: (values: UseFormStateReturn<TFieldValues>) => React.ReactNode}
</> FormProviderProps
export type FormProviderProps<TFieldValues extends FieldValues = FieldValues,TContext = any,TTransformedValues = TFieldValues,> = {children: React.ReactNode | React.ReactNode[]} & UseFormReturn<TFieldValues, TContext, TTransformedValues>
</> FormProps
export type FormProps<TFieldValues extends FieldValues,TTransformedValues = TFieldValues,> = Omit<React.FormHTMLAttributes<HTMLFormElement>, "onError" | "onSubmit"> &Partial<{control: Control<TFieldValues, any, TTransformedValues>headers: Record<string, string>validateStatus: (status: number) => booleanonError: (payload:| { response: Response; error?: undefined }| { response?: undefined; error: unknown }) => voidonSuccess: ({ response }: { response: Response }) => voidonSubmit: FormSubmitHandler<TTransformedValues>method: "post" | "put" | "delete"children: React.ReactNode | React.ReactNode[]render: (props: {submit: (e?: React.FormEvent) => void}) => React.ReactNode | React.ReactNode[]encType:| "application/x-www-form-urlencoded"| "multipart/form-data"| "text/plain"| "application/json"}>
</> UseFieldArrayReturn
export type UseFieldArrayReturn<TFieldValues extends FieldValues = FieldValues,TFieldArrayName extendsFieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,TKeyName extends string = "id",> = {swap: UseFieldArraySwapmove: UseFieldArrayMoveprepend: UseFieldArrayPrepend<TFieldValues, TFieldArrayName>append: UseFieldArrayAppend<TFieldValues, TFieldArrayName>remove: UseFieldArrayRemoveinsert: UseFieldArrayInsert<TFieldValues, TFieldArrayName>update: UseFieldArrayUpdate<TFieldValues, TFieldArrayName>replace: UseFieldArrayReplace<TFieldValues, TFieldArrayName>fields: (FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName> & {disabled?: boolean})[]}
</> FieldArrayMethodProps
Focus options accepted as the second argument to the append, prepend, and insert functions.
export type FieldArrayMethodProps = {shouldFocus?: booleanfocusIndex?: numberfocusName?: string}
</> UseFieldArrayProps
export type UseFieldArrayProps<TFieldValues extends FieldValues = FieldValues,TFieldArrayName extendsFieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,TKeyName extends string = "id",TTransformedValues = TFieldValues,> = {name: TFieldArrayNamekeyName?: TKeyNamecontrol?: Control<TFieldValues, any, TTransformedValues>rules?: {validate?:| Validate<FieldArray<TFieldValues, TFieldArrayName>[], TFieldValues>| Record<string,Validate<FieldArray<TFieldValues, TFieldArrayName>[], TFieldValues>>} & Pick<RegisterOptions<TFieldValues>,"maxLength" | "minLength" | "required">shouldUnregister?: booleandisabled?: boolean}
</> FieldArrayProps
export type FieldArrayProps<TFieldValues extends FieldValues = FieldValues,TFieldArrayName extendsFieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,TKeyName extends string = "id",> = {render: (fieldArray: UseFieldArrayReturn<TFieldValues, TFieldArrayName, TKeyName>) => React.ReactElement} & UseFieldArrayProps<TFieldValues, TFieldArrayName, TKeyName>
</> ControllerRenderProps
The shape of the field object passed to Controller's render prop. Useful for typing a custom controlled input component.
export type ControllerRenderProps<TFieldValues extends FieldValues = FieldValues,TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,> = {onChange: (...event: any[]) => voidonBlur: Noopvalue: FieldPathValue<TFieldValues, TName>disabled?: booleanname: TNameref: RefCallBack}
</> ControllerFieldState
The shape of the fieldState object passed to Controller's render prop.
export type ControllerFieldState = {invalid: booleanisTouched: booleanisDirty: booleanisValidating: booleanerror?: FieldError}
</> UseControllerReturn
export type UseControllerReturn<TFieldValues extends FieldValues = FieldValues,TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,> = {field: ControllerRenderProps<TFieldValues, TName>formState: UseFormStateReturn<TFieldValues>fieldState: ControllerFieldState}
</> UseControllerProps
export type UseControllerProps<TFieldValues extends FieldValues = FieldValues,TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,TTransformedValues = TFieldValues,> = {name: TNamerules?: Omit<RegisterOptions<TFieldValues, TName>,"valueAsNumber" | "valueAsDate" | "setValueAs" | "disabled">shouldUnregister?: booleandefaultValue?: FieldPathValue<TFieldValues, TName>control?: Control<TFieldValues, any, TTransformedValues>disabled?: booleanexact?: boolean}
</> ControllerProps
export type ControllerProps<TFieldValues extends FieldValues = FieldValues,TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,TTransformedValues = TFieldValues,> = {render: ({field,fieldState,formState,}: {field: ControllerRenderProps<TFieldValues, TName>fieldState: ControllerFieldStateformState: UseFormStateReturn<TFieldValues>}) => React.ReactElement} & UseControllerProps<TFieldValues, TName, TTransformedValues>
</> FieldError
export type FieldError = {type: LiteralUnion<keyof RegisterOptions, string>root?: FieldErrorref?: Reftypes?: MultipleFieldErrorsmessage?: Message}
</> FieldErrors
export type FieldErrors<T extends FieldValues = FieldValues> = Partial<FieldErrorsImpl<DeepRequired<T>>> & {root?: Record<string, GlobalError> & GlobalErrorform?: GlobalError}
</> ErrorOption
The error object accepted by setError.
export type ErrorOption = {message?: Messagetype?: LiteralUnion<keyof RegisterOptions, string>types?: MultipleFieldErrors}
</> Field
export type Field = {_f: {ref: Refname: InternalFieldNamerefs?: HTMLInputElement[]mount?: boolean} & RegisterOptions}
</> FieldPath
This type is useful when you define custom component's name prop, and it will type check against your field path.
export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>
</> FieldPathByValue
This type will return union with all available paths that match the passed value
export type FieldPathByValue<TFieldValues extends FieldValues, TValue> = {[Key in FieldPath<TFieldValues>]: FieldPathValue<TFieldValues,Key> extends TValue? Key: never}[FieldPath<TFieldValues>]
</> FieldValues
export type FieldValues = Record<string, any>
</> FieldArrayWithId
export type FieldArrayWithId<TFieldValues extends FieldValues = FieldValues,TFieldArrayName extendsFieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,TKeyName extends string = "id",> = FieldArray<TFieldValues, TFieldArrayName> & Record<TKeyName, string>
</> Mode
export type ValidationMode = typeof VALIDATION_MODEexport type Mode = keyof ValidationMode
</> RegisterOptions
export type RegisterOptions<TFieldValues extends FieldValues = FieldValues,TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,> = Partial<{required: Message | ValidationRule<boolean>min: ValidationRule<number | string>max: ValidationRule<number | string>maxLength: ValidationRule<number>minLength: ValidationRule<number>validate:| Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues>| Record<string,Validate<FieldPathValue<TFieldValues, TFieldName>, TFieldValues>>value: FieldPathValue<TFieldValues, TFieldName>setValueAs: (value: any) => anyshouldUnregister?: booleanonChange?: (event: any) => voidonBlur?: (event: any) => voiddisabled: booleandeps: FieldPath<TFieldValues> | FieldPath<TFieldValues>[]}> &(| {pattern?: ValidationRule<RegExp>valueAsNumber?: falsevalueAsDate?: false}| {pattern?: undefinedvalueAsNumber?: falsevalueAsDate?: true}| {pattern?: undefinedvalueAsNumber?: truevalueAsDate?: false})
</> FormState
The type of formState, returned by useForm and useFormState.
export type FormState<TFieldValues extends FieldValues> = {isDirty: booleanisLoading: booleanisSubmitted: booleanisSubmitSuccessful: booleanisSubmitting: booleanisValidating: booleanisValid: booleandisabled: booleansubmitCount: numberdefaultValues?: undefined | Readonly<DeepPartial<TFieldValues>>dirtyFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>touchedFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>validatingFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>errors: FieldErrors<TFieldValues>isReady: boolean}
</> KeepStateOptions
Options accepted by reset and useForm's resetOptions, controlling which parts of form state survive the reset.
export type KeepStateOptions = Partial<{keepDirtyValues: booleankeepErrors: booleankeepDirty: booleankeepValues: booleankeepDefaultValues: booleankeepIsSubmitted: booleankeepIsSubmitSuccessful: booleankeepTouched: booleankeepIsValidating: booleankeepIsValid: booleankeepSubmitCount: booleankeepFieldsRef: boolean}>
</> FormStateProxy
export type FormStateProxy<TFieldValues extends FieldValues = FieldValues> = {isDirty: booleanisValidating: booleandirtyFields: FieldNamesMarkedBoolean<TFieldValues>touchedFields: FieldNamesMarkedBoolean<TFieldValues>validatingFields: FieldNamesMarkedBoolean<TFieldValues>errors: booleanisValid: boolean}
</> NestedValue (Deprecated at 7.33.0)
NestedValue is deprecated and will be removed in a future major version. For
a non-primitive value that needs to stay controlled (such as the array values
below), type the field normally in your form values and wire it up with
Controller instead of wrapping the type in
NestedValue.
import { useEffect } from "react"import { useForm, NestedValue } from "react-hook-form"import { TextField, Select, MenuItem } from "@mui/material"import { Autocomplete } from "@mui/material"type Option = {label: stringvalue: string}const options = [{ label: "Chocolate", value: "chocolate" },{ label: "Strawberry", value: "strawberry" },{ label: "Vanilla", value: "vanilla" },]export default function App() {const {register,handleSubmit,watch,setValue,formState: { errors },} = useForm<{autocomplete: NestedValue<Option[]>select: NestedValue<number[]>}>({defaultValues: { autocomplete: [], select: [] },})const onSubmit = handleSubmit((data) => console.log(data))useEffect(() => {register("autocomplete", {validate: (value) => value.length || "This is required.",})register("select", {validate: (value) => value.length || "This is required.",})}, [register])return (<form onSubmit={onSubmit}><Autocompleteoptions={options}getOptionLabel={(option: Option) => option.label}onChange={(e, options) => setValue("autocomplete", options)}renderInput={(params) => (<TextField{...params}error={Boolean(errors?.autocomplete)}helperText={errors?.autocomplete?.message}/>)}/><Selectvalue=""multiple// cast required because NestedValue<number[]> bypasses field-level type inferenceonChange={(e) => setValue("select", e.target.value as number[])}><MenuItem value={10}>Ten</MenuItem><MenuItem value={20}>Twenty</MenuItem></Select><input type="submit" /></form>)}
Thank you for your support
If you find React Hook Form to be useful in your project, please consider starring and supporting it.