control
The control object for registering controlled inputs.
</> control: Control
This object contains methods for registering components into React Hook Form. Pass it to Controller, useController, useWatch, useFieldArray, useFormState, or FormProvider so those APIs can connect to the same form instance — it's optional wherever the component is already inside a FormProvider.
Important: do not access any of the properties inside this object directly; it is for internal use only.
Examples
A complete form where a reusable input component receives control as a prop. The parent owns the form; the child only connects to it.
import {useForm,useController,type Control,type FieldPath,type FieldValues,} from "react-hook-form"type FormValues = {firstName: stringemail: string}type TextInputProps<T extends FieldValues> = {control: Control<T>name: FieldPath<T>label: string}function TextInput<T extends FieldValues>({control,name,label,}: TextInputProps<T>) {const { field, fieldState } = useController({control,name,rules: { required: `${label} is required` },})return (<label>{label}<input {...field} aria-invalid={fieldState.invalid} />{fieldState.error && <p role="alert">{fieldState.error.message}</p>}</label>)}export default function App() {const { control, handleSubmit } = useForm<FormValues>({defaultValues: { firstName: "", email: "" },})const onSubmit = (data: FormValues) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><TextInput control={control} name="firstName" label="First name" /><TextInput control={control} name="email" label="Email" /><button type="submit">Submit</button></form>)}
import { useForm, useController } from "react-hook-form"function TextInput({ control, name, label }) {const { field, fieldState } = useController({control,name,rules: { required: `${label} is required` },})return (<label>{label}<input {...field} aria-invalid={fieldState.invalid} />{fieldState.error && <p role="alert">{fieldState.error.message}</p>}</label>)}export default function App() {const { control, handleSubmit } = useForm({defaultValues: { firstName: "", email: "" },})const onSubmit = (data) => console.log(data)return (<form onSubmit={handleSubmit(onSubmit)}><TextInput control={control} name="firstName" label="First name" /><TextInput control={control} name="email" label="Email" /><button type="submit">Submit</button></form>)}
Tips
- Think of
controlas an internal object that you only pass along. Give it to the APIs listed above, and do not read its properties or call its methods yourself. They are not part of the public API and can change in any release. - Inside a
FormProvider, you usually do not needcontrolat all. Hooks such asuseController,useWatch, anduseFormStateread it from context. - If you pass
controlas a prop, type it asControl<FormValues>, or make the component generic to reuse it across forms. See the TypeScript error below if the types don't line up.
Troubleshooting
Cannot read properties of null (reading '_names')
Why it happens
useController or Controller could not find a form. The component is rendered outside a FormProvider and no control prop was passed. Hooks such as useWatch fail the same way, with a different property name in the message.
Fix
Pass control from useForm, or wrap the tree in <FormProvider {...methods}>.
A field is missing from the submitted data
Why it happens
The field is connected to a different form than the one that submits. Usually useForm was called twice — once in the parent and again in a child — and the field received the child's control while the parent's handleSubmit runs.
Fix
Call useForm once and pass that control down, or use FormProvider.
Type 'Control<FormValues>' is not assignable to type 'Control<FieldValues>'
Why it happens
Control is tied to one exact form shape. A prop typed as Control<FieldValues>, or as a smaller shape such as Control<{ email: string }>, does not accept the control of a larger form.
Fix
For a component used by one form, type the prop as Control<FormValues>. For a component reused across forms, make it generic, as TextInput does in the example above. name is then checked against the form that owns control:
function TextInput<T extends FieldValues>(props: {control: Control<T>name: FieldPath<T>}) {// ...}