Luke UI
GitHub repository

React Hook Form

Wire Luke UI fields to React Hook Form with Controller.

React Hook Form owns the form state, and Luke UI renders the controls.

Initialise the form

Call useForm with defaultValues and a resolver, and keep the result as form. React Hook Form takes the form's value types from the Zod schema, so no hand-written type repeats it.

const schema = z.object({
	email: z.email('Enter an email address in the form you@example.com.'),
	name: z.string().min(1, 'Enter your name.'),
});

const form = useForm({
	defaultValues: { email: '', name: '' },
	resolver: zodResolver(schema),
});

Integrate components

Wrap each control in Controller. Its render prop hands you field and fieldState. Pass field.value, field.onChange, and field.onBlur to the control, and give field.ref to inputRef.

React Hook Form — Controller

A checkbox reads its value from isSelected.

React Hook Form — Checkbox

TextField and Checkbox render a label, description, and error message around the control, so inputRef is what reaches the input underneath. A primitive that renders the control itself, such as ComboboxInput, takes field.ref on ref.

Validation

Read the message from fieldState.error and pass it to errorMessage. The message marks the field invalid.

<Controller
	control={form.control}
	name="email"
	render={({ field, fieldState }) => (
		<TextField
			errorMessage={fieldState.error?.message}
			inputRef={field.ref}
			label="Email"
			onBlur={field.onBlur}
			onChange={field.onChange}
			validationBehavior="aria"
			value={field.value}
		/>
	)}
/>

Set validationBehavior="aria" on every field a Controller wraps. Read Validation for why native behaviour blocks the submit event before the library can run.

Focus the first invalid field

React Hook Form focuses the first invalid control after a failed submission, using the ref each field registered. A field that never receives field.ref stays unfocused, and the person filling in the form gets an error message without being taken to it.

Set shouldFocusError: false on useForm to turn this off.

Submitting data

Wrap the submit handler in form.handleSubmit. It runs the schema first, then calls the handler with the values.

<form onSubmit={form.handleSubmit((values) => saveAccount(values))}>

Continue learning