Skip to content
Nerdfishui
Esc
navigateopen⌘Jpreview

React Hook Form Example

Build forms in React using React Hook Form and Zod.

'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { Button } from '@nerdfish/react/button'
import {
	Card,
	CardContent,
	CardDescription,
	CardFooter,
	CardHeader,
	CardTitle,
} from '@nerdfish/react/card'
import {
	Field,
	FieldDescription,
	FieldError,
	FieldGroup,
	FieldLabel,
} from '@nerdfish/react/field'
import { Input } from '@nerdfish/react/input'
import {
	InputGroup,
	InputGroupAddon,
	InputGroupText,
	InputGroupTextarea,
} from '@nerdfish/react/input-group'
import { toast } from '@nerdfish/react/toast'
import { type CSSProperties } from 'react'
import { Controller, useForm } from 'react-hook-form'
import * as z from 'zod'

const formSchema = z.object({
	title: z
		.string()
		.min(5, 'Bug title must be at least 5 characters.')
		.max(32, 'Bug title must be at most 32 characters.'),
	description: z
		.string()
		.min(20, 'Description must be at least 20 characters.')
		.max(100, 'Description must be at most 100 characters.'),
})

export default function ReactHookFormExample() {
	const form = useForm<z.infer<typeof formSchema>>({
		resolver: zodResolver(formSchema),
		defaultValues: {
			title: '',
			description: '',
		},
	})

	function onSubmit(data: z.infer<typeof formSchema>) {
		toast('You submitted the following values:', {
			description: (
				<pre className="bg-muted/50 text-foreground p-friends mt-best-friends rounded-compact w-80 overflow-x-auto">
					<code>{JSON.stringify(data, null, 2)}</code>
				</pre>
			),
			position: 'bottom-right',
			classNames: {
				content: 'flex flex-col gap-best-friends',
			},
			style: {
				'--border-radius': 'calc(var(--radius)  + 4px)',
			} as CSSProperties,
		})
	}

	return (
		<Card className="w-full max-w-2xl">
			<CardHeader>
				<CardTitle>Bug Report</CardTitle>
				<CardDescription>
					Help us improve by reporting bugs you encounter.
				</CardDescription>
			</CardHeader>
			<CardContent>
				<form id="form-rhf-demo" onSubmit={form.handleSubmit(onSubmit)}>
					<FieldGroup>
						<Controller
							name="title"
							control={form.control}
							render={({ field, fieldState }) => (
								<Field data-invalid={fieldState.invalid}>
									<FieldLabel htmlFor="form-rhf-demo-title">
										Bug Title
									</FieldLabel>
									<Input
										{...field}
										id="form-rhf-demo-title"
										aria-invalid={fieldState.invalid}
										placeholder="Login button not working on mobile"
										autoComplete="off"
									/>
									{fieldState.invalid ? (
										<FieldError errors={[fieldState.error]} />
									) : null}
								</Field>
							)}
						/>
						<Controller
							name="description"
							control={form.control}
							render={({ field, fieldState }) => (
								<Field data-invalid={fieldState.invalid}>
									<FieldLabel htmlFor="form-rhf-demo-description">
										Description
									</FieldLabel>
									<InputGroup>
										<InputGroupTextarea
											{...field}
											id="form-rhf-demo-description"
											placeholder="I'm having an issue with the login button on mobile."
											rows={6}
											className="min-h-24 resize-none"
											aria-invalid={fieldState.invalid}
										/>
										<InputGroupAddon align="block-end">
											<InputGroupText className="tabular-nums">
												{field.value.length}/100 characters
											</InputGroupText>
										</InputGroupAddon>
									</InputGroup>
									<FieldDescription>
										Include steps to reproduce, expected behavior, and what
										actually happened.
									</FieldDescription>
									{fieldState.invalid ? (
										<FieldError errors={[fieldState.error]} />
									) : null}
								</Field>
							)}
						/>
					</FieldGroup>
				</form>
			</CardContent>
			<CardFooter>
				<Field orientation="horizontal">
					<Button type="button" variant="outline" onClick={() => form.reset()}>
						Reset
					</Button>
					<Button type="submit" form="form-rhf-demo">
						Submit
					</Button>
				</Field>
			</CardFooter>
		</Card>
	)
}