mcpField
R
react-hook-form

react-hook-form

github.com/claude-dev-suite开发4636 次安装3 天前更新
代码仓库

关于 react-hook-form

react-hook-form 是一项围绕 React Hook Form、Zod 和 shadcn/ui 表单组件的编码技能,提供表单设置、复杂验证和字段示例。它还列出常见表单问题及对应处理方法。

主要功能

  1. 01配置 React Hook Form 与 Zod
  2. 02构建复杂验证模式
  3. 03使用 shadcn/ui 表单组件
  4. 04处理选择框与日期选择字段
  5. 05排查重置、提交状态和类型问题

适用场景

  1. 01搭建带邮箱和密码验证的登录表单
  2. 02为用户资料定义字段约束
  3. 03在表单中加入选择框或日期字段
  4. 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>
  );
}

关键模式

模式描述
zodResolverZod 验证集成
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