关于 react-hook-form
react-hook-form 是一项围绕 React Hook Form、Zod 和 shadcn/ui 表单组件的编码技能,提供表单设置、复杂验证和字段示例。它还列出常见表单问题及对应处理方法。
主要功能
- 01配置 React Hook Form 与 Zod
- 02构建复杂验证模式
- 03使用 shadcn/ui 表单组件
- 04处理选择框与日期选择字段
- 05排查重置、提交状态和类型问题
适用场景
- 01搭建带邮箱和密码验证的登录表单
- 02为用户资料定义字段约束
- 03在表单中加入选择框或日期字段
- 04修复默认值或提交状态问题
MCP 相关
该技能建议通过 `mcp__documentation__fetch_docs` 获取 React Hook Form 与 Zod、shadcn/ui 集成的详细文档。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill react-hook-form安装后在 Claude Code 中输入/react-hook-form确认已加载。
详细介绍
使用 Zod 的 React Hook Form
深入了解:使用 technology:
react-hook-form的mcp__documentation__fetch_docs获取 React Hook Form 与 Zod 和 shadcn/ui 集成的全面文档。
表单设置
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type LoginFormData = z.infer<typeof loginSchema>;
function LoginForm() {
const form = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
},
});
const onSubmit = async (data: LoginFormData) => {
await login(data);
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* fields */}
</form>
);
}
使用 shadcn/ui 表单组件
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
function LoginForm() {
const form = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: { email: '', password: '' },
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="[email protected]" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Loading...' : 'Login'}
</Button>
</form>
</Form>
);
}
复杂验证 Schema
const userSchema = z.object({
name: z.string()
.min(2, 'Name must be at least 2 characters')
.max(100, 'Name must be less than 100 characters'),
email: z.string().email('Invalid email'),
role: z.enum(['admin', 'user', 'manager'], {
errorMap: () => ({ message: 'Select a valid role' }),
}),
department: z.string().optional(),
startDate: z.date({
required_error: 'Start date is required',
}),
salary: z.number()
.min(0, 'Salary must be positive')
.optional(),
});
// Conditional validation
const formSchema = z.object({
hasAddress: z.boolean(),
address: z.string().optional(),
}).refine(
(data) => !data.hasAddress || data.address,
{ message: 'Address is required', path: ['address'] }
);
选择字段
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem>
<FormLabel>Role</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select role" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="manager">Manager</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
日期选择器字段
import { Calendar } from '@/components/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { format } from 'date-fns';
import { CalendarIcon } from 'lucide-react';
<FormField
control={form.control}
name="startDate"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Start Date</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button variant="outline" className="w-full justify-start text-left">
<CalendarIcon className="mr-2 h-4 w-4" />
{field.value ? format(field.value, 'PPP') : 'Pick a date'}
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={field.value}
onSelect={field.onChange}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
带 API Mutation 的表单
import { useMutation, useQueryClient } from '@tanstack/react-query';
function UserForm({ userId }: { userId?: string }) {
const queryClient = useQueryClient();
const isEditing = !!userId;
const mutation = useMutation({
mutationFn: isEditing ? usersApi.update : usersApi.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
toast.success(isEditing ? 'User updated' : 'User created');
},
onError: (error) => {
toast.error(error.message);
},
});
const onSubmit = (data: UserFormData) => {
if (isEditing) {
mutation.mutate({ id: userId, ...data });
} else {
mutation.mutate(data);
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* fields */}
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Saving...' : 'Save'}
</Button>
</form>
</Form>
);
}
关键模式
| 模式 | 描述 |
|---|---|
zodResolver | Zod 验证集成 |
form.formState | 表单状态(isSubmitting、errors 等) |
form.reset() | 将表单重置为默认值 |
form.setValue() | 以编程方式设置值 |
form.watch() | 监听字段更改 |
form.trigger() | 触发验证 |
何时不应使用此技能
- React 19 Server Actions 表单 - 使用
react-19技能了解 useActionState 模式 - 基础表单处理 - 使用
react-forms技能了解通用表单模式 - 非 shadcn/ui 组件 - 调整模式,或使用
react-forms技能 - 非 Zod 验证 - 使用
react-forms技能了解其他验证库
反模式
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 未使用 zodResolver | 手动验证,样板代码繁多 | 使用 Zod 时始终使用 zodResolver |
| 缺少 FormField 包装器 | 没有错误处理,用户体验差 | 使用 FormField 包装输入框 |
| 未检查 formState.isSubmitting | 提交期间按钮未禁用 | 使用 isSubmitting 禁用按钮 |
| 使用 register 却未使用 FormField | 未集成 shadcn/ui | 使用带 Controller 的 FormField |
| 成功后未调用 reset() | 表单未清空 | 提交成功时调用 form.reset() |
| 在组件中进行复杂验证 | 难以测试和维护 | 使用 Zod 定义 schema |
| 未处理 mutation 错误 | 失败未提示 | 在 mutation 中使用 onError |
快速故障排除
| 问题 | 可能原因 | 修复方法 |
|---|---|---|
| 验证不起作用 | 缺少 zodResolver | 在 useForm 中添加 resolver: zodResolver(schema) |
| 错误未显示 | 缺少 FormMessage | 在 FormField 中添加 |
| 表单未重置 | 未调用 reset() | 提交成功后调用 form.reset() |
| 提交按钮未禁用 | 未使用 isSubmitting | 使用 form.formState.isSubmitting |
| 自定义组件不起作用 | 未使用 Controller | 在 FormField 中使用 Controller 包装 |
| 未显示默认值 | 未设置在 defaultValues 中 | 在 useForm 的 defaultValues 中添加 |
| 类型错误 | Schema 与 FormData 类型不匹配 | 确保 z.infer 与接口匹配 |
参考文档
评论
支持 Markdown