# React Hook Form (/docs/react-hook-form)



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

## Initialise the form [#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.

```tsx
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 [#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`.

apps/docs/src/examples/forms/react-hook-form.tsx

```tsx
import { zodResolver } from '@hookform/resolvers/zod';
import { Box } from '@luke-ui/react/box';
import { Button } from '@luke-ui/react/button';
import { Text } from '@luke-ui/react/text';
import { TextField } from '@luke-ui/react/text-field';
import { Controller, useForm } from 'react-hook-form';
import * as z from 'zod';

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.'),
});

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

	return (
		<Box display="flex" flexDirection="column" gap="sp16" maxInlineSize="20rem">
			<form onSubmit={form.handleSubmit(() => undefined)}>
				<Box display="flex" flexDirection="column" gap="sp16">
					<Controller
						control={form.control}
						name="name"
						render={({ field, fieldState }) => (
							<TextField
								errorMessage={fieldState.error?.message}
								inputRef={field.ref}
								label="Name"
								onBlur={field.onBlur}
								onChange={field.onChange}
								validationBehavior="aria"
								value={field.value}
							/>
						)}
					/>
					<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}
							/>
						)}
					/>
					<Box>
						<Button type="submit">Create account</Button>
					</Box>
				</Box>
			</form>
			<Text elementType="p" role="status">
				{form.formState.isSubmitSuccessful ? `Submitted: ${form.getValues('name')}` : null}
			</Text>
		</Box>
	);
};
```

A checkbox reads its value from `isSelected`.

apps/docs/src/examples/forms/react-hook-form-checkbox.tsx

```tsx
import { zodResolver } from '@hookform/resolvers/zod';
import { Box } from '@luke-ui/react/box';
import { Button } from '@luke-ui/react/button';
import { Checkbox } from '@luke-ui/react/checkbox';
import { Text } from '@luke-ui/react/text';
import { Controller, useForm } from 'react-hook-form';
import * as z from 'zod';

const schema = z.object({
	terms: z.boolean().refine((accepted) => accepted, {
		error: 'Accept the terms of service before you continue.',
	}),
});

export default () => {
	const form = useForm({
		defaultValues: { terms: false },
		resolver: zodResolver(schema),
	});

	return (
		<Box display="flex" flexDirection="column" gap="sp16" maxInlineSize="20rem">
			<form onSubmit={form.handleSubmit(() => undefined)}>
				<Box display="flex" flexDirection="column" gap="sp16">
					<Controller
						control={form.control}
						name="terms"
						render={({ field, fieldState }) => (
							<Checkbox
								errorMessage={fieldState.error?.message}
								inputRef={field.ref}
								isSelected={field.value}
								onBlur={field.onBlur}
								onChange={field.onChange}
								validationBehavior="aria"
							>
								I accept the terms of service
							</Checkbox>
						)}
					/>
					<Box>
						<Button type="submit">Continue</Button>
					</Box>
				</Box>
			</form>
			<Text elementType="p" role="status">
				{form.formState.isSubmitSuccessful ? 'Terms accepted.' : null}
			</Text>
		</Box>
	);
};
```

`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 [#validation]

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

```tsx
<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](/docs/validation#let-the-browser-validate) for why native behaviour blocks the submit
event before the library can run.

## Focus the first invalid field [#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 [#submitting-data]

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

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

## Continue learning [#continue-learning]

<Cards>
  <Card href="/docs/validation" title="Validation">
    Choose validationBehavior and write the error message.
  </Card>

  <Card href="/docs/forms" title="Forms">
    Build the same form with native submit and reset.
  </Card>

  <Card href="/docs/tanstack-form" title="TanStack Form">
    See the same pattern with form.Field.
  </Card>
</Cards>
