form-generator-rhf-zod

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Form Generator with React Hook Form & Zod

基于React Hook Form & Zod的表单生成器

Generate production-ready React forms using React Hook Form, Zod validation schemas, and accessible shadcn/ui form controls. This skill creates forms with client-side and server-side validation, proper TypeScript types, and consistent error handling.
使用React Hook Form、Zod验证Schema和可访问的shadcn/ui表单控件,生成可用于生产环境的React表单。此技能生成的表单支持客户端与服务端双重验证,具备规范的TypeScript类型定义,以及一致的错误处理机制。

When to Use This Skill

何时使用此技能

Apply this skill when:
  • Creating forms for entities (characters, locations, items, factions)
  • Building data entry interfaces with validation requirements
  • Generating forms with complex field types and conditional logic
  • Setting up forms that need both client and server validation
  • Creating accessible forms with proper ARIA attributes
  • Building forms with multi-step or wizard patterns
在以下场景中应用此技能:
  • 为实体(角色、位置、物品、派系)创建表单
  • 构建带有验证要求的数据输入界面
  • 生成包含复杂字段类型和条件逻辑的表单
  • 设置需要客户端与服务端双重验证的表单
  • 创建具备ARIA属性的可访问表单
  • 构建多步骤或向导式表单

Resources Available

可用资源

Scripts

脚本

scripts/generate_form.py - Generates form component, Zod schema, and server action from field specifications.
Usage:
bash
python scripts/generate_form.py --name CharacterForm --fields fields.json --output components/forms
scripts/generate_zod_schema.py - Converts field specifications to Zod schema with validation rules.
Usage:
bash
python scripts/generate_zod_schema.py --fields fields.json --output lib/schemas
scripts/generate_form.py - 根据字段规格生成表单组件、Zod Schema和服务端操作函数。
使用方式:
bash
python scripts/generate_form.py --name CharacterForm --fields fields.json --output components/forms
scripts/generate_zod_schema.py - 将字段规格转换为带有验证规则的Zod Schema。
使用方式:
bash
python scripts/generate_zod_schema.py --fields fields.json --output lib/schemas

References

参考文档

references/rhf-patterns.md - React Hook Form patterns, hooks, and best practices references/zod-validation.md - Zod schema patterns, refinements, and custom validators references/shadcn-form-controls.md - shadcn/ui form component usage and examples references/server-actions.md - Server action patterns for form submission
references/rhf-patterns.md - React Hook Form的模式、钩子函数与最佳实践 references/zod-validation.md - Zod Schema的模式、自定义验证与转换规则 references/shadcn-form-controls.md - shadcn/ui表单组件的使用方法与示例 references/server-actions.md - 表单提交的服务端操作模式

Assets

资源模板

assets/form-template.tsx - Base form component template with RHF setup assets/field-templates/ - Individual field component templates (Input, Textarea, Select, Checkbox, etc.) assets/validation-schemas.ts - Common Zod validation patterns assets/form-utils.ts - Form utility functions (formatters, transformers, validators)
assets/form-template.tsx - 已配置React Hook Form的基础表单组件模板 assets/field-templates/ - 各类型字段的组件模板(Input、Textarea、Select、Checkbox等) assets/validation-schemas.ts - 通用Zod验证模式 assets/form-utils.ts - 表单工具函数(格式化、转换、验证)

Form Generation Process

表单生成流程

Step 1: Define Field Specifications

步骤1:定义字段规格

Create a field specification file describing form fields, types, validation rules, and UI properties.
Field specification format:
json
{
  "fields": [
    {
      "name": "characterName",
      "label": "Character Name",
      "type": "text",
      "required": true,
      "validation": {
        "minLength": 2,
        "maxLength": 100,
        "pattern": "^[a-zA-Z\\s'-]+$"
      },
      "placeholder": "Enter character name",
      "helpText": "The character's full name as it appears in your world"
    },
    {
      "name": "age",
      "label": "Age",
      "type": "number",
      "required": false,
      "validation": {
        "min": 0,
        "max": 10000
      }
    },
    {
      "name": "faction",
      "label": "Faction",
      "type": "select",
      "required": true,
      "options": "dynamic",
      "optionsSource": "api.getFactions()"
    },
    {
      "name": "biography",
      "label": "Biography",
      "type": "textarea",
      "required": false,
      "validation": {
        "maxLength": 5000
      },
      "rows": 8
    }
  ],
  "formOptions": {
    "submitLabel": "Create Character",
    "resetLabel": "Clear Form",
    "showReset": true,
    "successMessage": "Character created successfully",
    "errorMessage": "Failed to create character"
  }
}
创建字段规格文件,描述表单字段的类型、验证规则和UI属性。
字段规格格式:
json
{
  "fields": [
    {
      "name": "characterName",
      "label": "Character Name",
      "type": "text",
      "required": true,
      "validation": {
        "minLength": 2,
        "maxLength": 100,
        "pattern": "^[a-zA-Z\\s'-]+$"
      },
      "placeholder": "Enter character name",
      "helpText": "The character's full name as it appears in your world"
    },
    {
      "name": "age",
      "label": "Age",
      "type": "number",
      "required": false,
      "validation": {
        "min": 0,
        "max": 10000
      }
    },
    {
      "name": "faction",
      "label": "Faction",
      "type": "select",
      "required": true,
      "options": "dynamic",
      "optionsSource": "api.getFactions()"
    },
    {
      "name": "biography",
      "label": "Biography",
      "type": "textarea",
      "required": false,
      "validation": {
        "maxLength": 5000
      },
      "rows": 8
    }
  ],
  "formOptions": {
    "submitLabel": "Create Character",
    "resetLabel": "Clear Form",
    "showReset": true,
    "successMessage": "Character created successfully",
    "errorMessage": "Failed to create character"
  }
}

Step 2: Generate Zod Schema

步骤2:生成Zod Schema

Use scripts/generate_zod_schema.py to create type-safe validation schema:
bash
python scripts/generate_zod_schema.py --fields character-fields.json --output lib/schemas/character.ts
Generated schema includes:
  • Field-level validation rules
  • Custom refinements and transformations
  • Type inference for TypeScript
  • Error message customization
  • Server-side validation support
使用scripts/generate_zod_schema.py创建类型安全的验证Schema:
bash
python scripts/generate_zod_schema.py --fields character-fields.json --output lib/schemas/character.ts
生成的Schema包含:
  • 字段级验证规则
  • 自定义验证与转换逻辑
  • TypeScript类型推导
  • 错误消息自定义
  • 服务端验证支持

Step 3: Generate Form Component

步骤3:生成表单组件

Use scripts/generate_form.py to create React Hook Form component:
bash
python scripts/generate_form.py --name CharacterForm --fields character-fields.json --output components/forms
Generated component includes:
  • React Hook Form setup with useForm hook
  • Zod schema resolver integration
  • shadcn/ui FormField components
  • Proper TypeScript types inferred from schema
  • Accessible form controls with ARIA labels
  • Error display with FormMessage components
  • Form submission handler with loading states
  • Success/error toast notifications
使用scripts/generate_form.py创建React Hook Form组件:
bash
python scripts/generate_form.py --name CharacterForm --fields character-fields.json --output components/forms
生成的组件包含:
  • 使用useForm钩子配置React Hook Form
  • Zod Schema解析器集成
  • shadcn/ui FormField组件
  • 从Schema推导的TypeScript类型
  • 带有ARIA标签的可访问表单控件
  • 使用FormMessage组件展示错误信息
  • 包含加载状态的表单提交处理器
  • 成功/错误提示的Toast通知

Step 4: Create Server Action

步骤4:创建服务端操作函数

Generate server action for form submission with server-side validation:
typescript
'use server'

import { z } from 'zod'
import { characterSchema } from '@/lib/schemas/character'
import { createCharacter } from '@/lib/db/characters'

export async function createCharacterAction(data: z.infer<typeof characterSchema>) {
  // Server-side validation
  const validated = characterSchema.safeParse(data)

  if (!validated.success) {
    return {
      success: false,
      errors: validated.error.flatten().fieldErrors
    }
  }

  // Database operation
  const character = await createCharacter(validated.data)

  return {
    success: true,
    data: character
  }
}
生成用于表单提交的服务端操作函数,支持服务端验证:
typescript
'use server'

import { z } from 'zod'
import { characterSchema } from '@/lib/schemas/character'
import { createCharacter } from '@/lib/db/characters'

export async function createCharacterAction(data: z.infer<typeof characterSchema>) {
  // 服务端验证
  const validated = characterSchema.safeParse(data)

  if (!validated.success) {
    return {
      success: false,
      errors: validated.error.flatten().fieldErrors
    }
  }

  // 数据库操作
  const character = await createCharacter(validated.data)

  return {
    success: true,
    data: character
  }
}

Step 5: Integrate Form into Page

步骤5:将表单集成到页面

Import and use generated form component in page or parent component:
tsx
import { CharacterForm } from '@/components/forms/CharacterForm'

export default function CreateCharacterPage() {
  return (
    <div className="container max-w-2xl py-8">
      <h1 className="text-3xl font-bold mb-6">Create New Character</h1>
      <CharacterForm />
    </div>
  )
}
在页面或父组件中导入并使用生成的表单组件:
tsx
import { CharacterForm } from '@/components/forms/CharacterForm'

export default function CreateCharacterPage() {
  return (
    <div className="container max-w-2xl py-8">
      <h1 className="text-3xl font-bold mb-6">Create New Character</h1>
      <CharacterForm />
    </div>
  )
}

Field Type Support

支持的字段类型

Supported field types and their shadcn/ui mappings:
  • text → Input (type="text")
  • email → Input (type="email")
  • password → Input (type="password")
  • number → Input (type="number")
  • tel → Input (type="tel")
  • url → Input (type="url")
  • textarea → Textarea
  • select → Select with SelectTrigger/SelectContent
  • multiselect → MultiSelect custom component
  • checkbox → Checkbox
  • radio → RadioGroup with RadioGroupItem
  • switch → Switch
  • date → DatePicker (Popover + Calendar)
  • datetime → DateTimePicker custom component
  • file → Input (type="file")
  • combobox → Combobox (Command + Popover)
  • tags → TagInput custom component
  • slider → Slider
  • color → ColorPicker custom component
支持的字段类型及其对应的shadcn/ui组件映射:
  • text → Input (type="text")
  • email → Input (type="email")
  • password → Input (type="password")
  • number → Input (type="number")
  • tel → Input (type="tel")
  • url → Input (type="url")
  • textarea → Textarea
  • select → Select(搭配SelectTrigger/SelectContent)
  • multiselect → 自定义MultiSelect组件
  • checkbox → Checkbox
  • radio → RadioGroup(搭配RadioGroupItem)
  • switch → Switch
  • date → DatePicker(Popover + Calendar)
  • datetime → 自定义DateTimePicker组件
  • file → Input (type="file")
  • combobox → Combobox(Command + Popover)
  • tags → 自定义TagInput组件
  • slider → Slider
  • color → 自定义ColorPicker组件

Validation Patterns

验证模式

Common validation patterns using Zod:
使用Zod实现的常见验证模式:

String Validation

字符串验证

typescript
// Required with length constraints
z.string().min(2, "Too short").max(100, "Too long")

// Email
z.string().email("Invalid email")

// URL
z.string().url("Invalid URL")

// Pattern matching
z.string().regex(/^[a-zA-Z]+$/, "Letters only")

// Trimmed strings
z.string().trim().min(1)

// Custom transformation
z.string().transform(val => val.toLowerCase())
typescript
// 必填且有长度限制
z.string().min(2, "Too short").max(100, "Too long")

// 邮箱格式
z.string().email("Invalid email")

// URL格式
z.string().url("Invalid URL")

// 正则匹配
z.string().regex(/^[a-zA-Z]+$/, "Letters only")

// 去除首尾空格
z.string().trim().min(1)

// 自定义转换
z.string().transform(val => val.toLowerCase())

Number Validation

数字验证

typescript
// Range validation
z.number().min(0).max(100)

// Integer only
z.number().int("Must be whole number")

// Positive numbers
z.number().positive("Must be positive")

// Custom refinement
z.number().refine(val => val % 5 === 0, "Must be multiple of 5")
typescript
// 范围验证
z.number().min(0).max(100)

// 仅允许整数
z.number().int("Must be whole number")

// 正数验证
z.number().positive("Must be positive")

// 自定义验证
z.number().refine(val => val % 5 === 0, "Must be multiple of 5")

Array Validation

数组验证

typescript
// Array with min/max items
z.array(z.string()).min(1, "Select at least one").max(5, "Too many")

// Non-empty array
z.array(z.string()).nonempty("Required")
typescript
// 数组长度限制
z.array(z.string()).min(1, "Select at least one").max(5, "Too many")

// 非空数组
z.array(z.string()).nonempty("Required")

Object Validation

对象验证

typescript
// Nested objects
z.object({
  address: z.object({
    street: z.string(),
    city: z.string(),
    zipCode: z.string().regex(/^\d{5}$/)
  })
})
typescript
// 嵌套对象
z.object({
  address: z.object({
    street: z.string(),
    city: z.string(),
    zipCode: z.string().regex(/^\d{5}$/)
  })
})

Conditional Validation

条件验证

typescript
// Refine with cross-field validation
z.object({
  password: z.string().min(8),
  confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
  message: "Passwords must match",
  path: ["confirmPassword"]
})
typescript
// 跨字段验证
z.object({
  password: z.string().min(8),
  confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
  message: "Passwords must match",
  path: ["confirmPassword"]
})

Optional and Nullable Fields

可选与可空字段

typescript
// Optional (can be undefined)
z.string().optional()

// Nullable (can be null)
z.string().nullable()

// Optional with default
z.string().default("default value")
typescript
// 可选字段(可为undefined)
z.string().optional()

// 可空字段(可为null)
z.string().nullable()

// 带默认值的可选字段
z.string().default("default value")

Form Patterns

表单模式

Basic Form Structure

基础表单结构

tsx
'use client'

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { toast } from 'sonner'

const formSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email()
})

type FormValues = z.infer<typeof formSchema>

export function ExampleForm() {
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: '',
      email: ''
    }
  })

  async function onSubmit(values: FormValues) {
    try {
      const result = await submitAction(values)
      if (result.success) {
        toast.success('Submitted successfully')
        form.reset()
      } else {
        toast.error(result.message)
      }
    } catch (error) {
      toast.error('An error occurred')
    }
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input placeholder="Enter name" {...field} />
              </FormControl>
              <FormDescription>Your display name</FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="you@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <Button type="submit" disabled={form.formState.isSubmitting}>
          {form.formState.isSubmitting ? 'Submitting...' : 'Submit'}
        </Button>
      </form>
    </Form>
  )
}
tsx
'use client'

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { toast } from 'sonner'

const formSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email()
})

type FormValues = z.infer<typeof formSchema>

export function ExampleForm() {
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: '',
      email: ''
    }
  })

  async function onSubmit(values: FormValues) {
    try {
      const result = await submitAction(values)
      if (result.success) {
        toast.success('Submitted successfully')
        form.reset()
      } else {
        toast.error(result.message)
      }
    } catch (error) {
      toast.error('An error occurred')
    }
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Name</FormLabel>
              <FormControl>
                <Input placeholder="Enter name" {...field} />
              </FormControl>
              <FormDescription>Your display name</FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="you@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <Button type="submit" disabled={form.formState.isSubmitting}>
          {form.formState.isSubmitting ? 'Submitting...' : 'Submit'}
        </Button>
      </form>
    </Form>
  )
}

Array Fields with useFieldArray

使用useFieldArray处理数组字段

tsx
import { useFieldArray } from 'react-hook-form'
import { Button } from '@/components/ui/button'

// In schema
const formSchema = z.object({
  tags: z.array(z.object({
    value: z.string().min(1)
  })).min(1)
})

// In component
const { fields, append, remove } = useFieldArray({
  control: form.control,
  name: 'tags'
})

// In JSX
{fields.map((field, index) => (
  <div key={field.id} className="flex gap-2">
    <FormField
      control={form.control}
      name={`tags.${index}.value`}
      render={({ field }) => (
        <FormItem className="flex-1">
          <FormControl>
            <Input {...field} />
          </FormControl>
          <FormMessage />
        </FormItem>
      )}
    />
    <Button type="button" variant="destructive" size="icon" onClick={() => remove(index)}>
      X
    </Button>
  </div>
))}
<Button type="button" onClick={() => append({ value: '' })}>
  Add Tag
</Button>
tsx
import { useFieldArray } from 'react-hook-form'
import { Button } from '@/components/ui/button'

// 在Schema中定义
const formSchema = z.object({
  tags: z.array(z.object({
    value: z.string().min(1)
  })).min(1)
})

// 在组件中使用
const { fields, append, remove } = useFieldArray({
  control: form.control,
  name: 'tags'
})

// 在JSX中渲染
{fields.map((field, index) => (
  <div key={field.id} className="flex gap-2">
    <FormField
      control={form.control}
      name={`tags.${index}.value`}
      render={({ field }) => (
        <FormItem className="flex-1">
          <FormControl>
            <Input {...field} />
          </FormControl>
          <FormMessage />
        </FormItem>
      )}
    />
    <Button type="button" variant="destructive" size="icon" onClick={() => remove(index)}>
      X
    </Button>
  </div>
))}
<Button type="button" onClick={() => append({ value: '' })}>
  Add Tag
</Button>

File Upload with Preview

带预览的文件上传

tsx
const [preview, setPreview] = useState<string | null>(null)

<FormField
  control={form.control}
  name="avatar"
  render={({ field: { value, onChange, ...field } }) => (
    <FormItem>
      <FormLabel>Avatar</FormLabel>
      <FormControl>
        <Input
          type="file"
          accept="image/*"
          {...field}
          onChange={(e) => {
            const file = e.target.files?.[0]
            if (file) {
              onChange(file)
              const reader = new FileReader()
              reader.onloadend = () => setPreview(reader.result as string)
              reader.readAsDataURL(file)
            }
          }}
        />
      </FormControl>
      {preview && (
        <img src={preview} alt="Preview" className="mt-2 h-32 w-32 object-cover rounded" />
      )}
      <FormMessage />
    </FormItem>
  )}
/>
tsx
const [preview, setPreview] = useState<string | null>(null)

<FormField
  control={form.control}
  name="avatar"
  render={({ field: { value, onChange, ...field } }) => (
    <FormItem>
      <FormLabel>Avatar</FormLabel>
      <FormControl>
        <Input
          type="file"
          accept="image/*"
          {...field}
          onChange={(e) => {
            const file = e.target.files?.[0]
            if (file) {
              onChange(file)
              const reader = new FileReader()
              reader.onloadend = () => setPreview(reader.result as string)
              reader.readAsDataURL(file)
            }
          }}
        />
      </FormControl>
      {preview && (
        <img src={preview} alt="Preview" className="mt-2 h-32 w-32 object-cover rounded" />
      )}
      <FormMessage />
    </FormItem>
  )}
/>

Conditional Fields

条件字段

tsx
const showAdvanced = form.watch('showAdvanced')

<FormField
  control={form.control}
  name="showAdvanced"
  render={({ field }) => (
    <FormItem className="flex items-center gap-2">
      <FormControl>
        <Switch checked={field.value} onCheckedChange={field.onChange} />
      </FormControl>
      <FormLabel>Show Advanced Options</FormLabel>
    </FormItem>
  )}
/>

{showAdvanced && (
  <FormField
    control={form.control}
    name="advancedOption"
    render={({ field }) => (
      <FormItem>
        <FormLabel>Advanced Option</FormLabel>
        <FormControl>
          <Input {...field} />
        </FormControl>
        <FormMessage />
      </FormItem>
    )}
  />
)}
tsx
const showAdvanced = form.watch('showAdvanced')

<FormField
  control={form.control}
  name="showAdvanced"
  render={({ field }) => (
    <FormItem className="flex items-center gap-2">
      <FormControl>
        <Switch checked={field.value} onCheckedChange={field.onChange} />
      </FormControl>
      <FormLabel>Show Advanced Options</FormLabel>
    </FormItem>
  )}
/>

{showAdvanced && (
  <FormField
    control={form.control}
    name="advancedOption"
    render={({ field }) => (
      <FormItem>
        <FormLabel>Advanced Option</FormLabel>
        <FormControl>
          <Input {...field} />
        </FormControl>
        <FormMessage />
      </FormItem>
    )}
  />
)}

Accessibility Considerations

可访问性注意事项

Ensure forms are accessible by:
  1. Proper Labels: Every form control must have an associated FormLabel
  2. Error Messages: Use FormMessage to announce validation errors
  3. Descriptions: Use FormDescription for helpful context
  4. Required Fields: Mark required fields visually and in ARIA attributes
  5. Focus Management: Ensure logical tab order and focus indicators
  6. Keyboard Navigation: All controls operable via keyboard
  7. ARIA Attributes: FormField automatically sets aria-describedby and aria-invalid
  8. Error Summary: Consider adding error summary at top of form for screen readers
确保表单具备可访问性,需做到:
  1. 规范标签:每个表单控件必须关联对应的FormLabel
  2. 错误提示:使用FormMessage播报验证错误
  3. 辅助说明:使用FormDescription提供上下文帮助
  4. 必填标识:视觉上和ARIA属性中标记必填字段
  5. 焦点管理:确保合理的Tab切换顺序和焦点指示器
  6. 键盘导航:所有控件支持键盘操作
  7. ARIA属性:FormField自动设置aria-describedby和aria-invalid属性
  8. 错误汇总:考虑在表单顶部添加错误汇总,方便屏幕阅读器用户查看

Testing Generated Forms

生成表单的测试

Test forms using React Testing Library and Vitest:
tsx
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { CharacterForm } from './CharacterForm'

describe('CharacterForm', () => {
  it('validates required fields', async () => {
    render(<CharacterForm />)

    const submitButton = screen.getByRole('button', { name: /submit/i })
    await userEvent.click(submitButton)

    expect(await screen.findByText(/name is required/i)).toBeInTheDocument()
  })

  it('submits valid data', async () => {
    const mockSubmit = vi.fn()
    render(<CharacterForm onSubmit={mockSubmit} />)

    await userEvent.type(screen.getByLabelText(/name/i), 'Aragorn')
    await userEvent.click(screen.getByRole('button', { name: /submit/i }))

    await waitFor(() => {
      expect(mockSubmit).toHaveBeenCalledWith({
        name: 'Aragorn'
      })
    })
  })
})
使用React Testing Library和Vitest测试表单:
tsx
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { CharacterForm } from './CharacterForm'

describe('CharacterForm', () => {
  it('validates required fields', async () => {
    render(<CharacterForm />)

    const submitButton = screen.getByRole('button', { name: /submit/i })
    await userEvent.click(submitButton)

    expect(await screen.findByText(/name is required/i)).toBeInTheDocument()
  })

  it('submits valid data', async () => {
    const mockSubmit = vi.fn()
    render(<CharacterForm onSubmit={mockSubmit} />)

    await userEvent.type(screen.getByLabelText(/name/i), 'Aragorn')
    await userEvent.click(screen.getByRole('button', { name: /submit/i }))

    await waitFor(() => {
      expect(mockSubmit).toHaveBeenCalledWith({
        name: 'Aragorn'
      })
    })
  })
})

Common Use Cases for Worldbuilding

世界观构建的常见场景

Character Creation Form

角色创建表单

Fields: name, race, faction, class, age, appearance, biography, relationships, attributes, inventory
字段:名称、种族、派系、职业、年龄、外貌、传记、关系、属性、物品栏

Location Form

位置表单

Fields: name, type, region, coordinates, climate, population, government, description, points of interest
字段:名称、类型、区域、坐标、气候、人口、政府、描述、兴趣点

Item/Artifact Form

物品/神器表单

Fields: name, type, rarity, owner, location, properties, history, magical effects, value
字段:名称、类型、稀有度、所有者、位置、属性、历史、魔法效果、价值

Event/Timeline Form

事件/时间线表单

Fields: title, date, location, participants, description, consequences, related events
字段:标题、日期、位置、参与者、描述、影响、相关事件

Faction/Organization Form

派系/组织表单

Fields: name, type, leader, headquarters, goals, allies, enemies, members, history
字段:名称、类型、领袖、总部、目标、盟友、敌人、成员、历史

Implementation Checklist

实现检查清单

When generating forms, ensure:
  • Zod schema created with all validation rules
  • Form component uses zodResolver
  • All field types mapped to appropriate shadcn/ui components
  • FormField used for each field with proper render prop
  • FormLabel, FormControl, FormMessage included for each field
  • Form submission handler with error handling
  • Loading states during submission
  • Success/error feedback (toasts or messages)
  • Server action created with server-side validation
  • TypeScript types inferred from Zod schema
  • Accessibility attributes present
  • Form reset after successful submission
  • Proper default values set
生成表单时,需确保:
  • 创建包含所有验证规则的Zod Schema
  • 表单组件使用zodResolver
  • 所有字段类型映射到对应的shadcn/ui组件
  • 每个字段使用FormField并配置正确的渲染属性
  • 每个字段包含FormLabel、FormControl、FormMessage
  • 表单提交处理器包含错误处理逻辑
  • 提交过程中显示加载状态
  • 提供成功/错误反馈(Toast或消息提示)
  • 创建带有服务端验证的服务端操作函数
  • 从Zod Schema推导TypeScript类型
  • 包含可访问性属性
  • 表单提交成功后重置
  • 设置合理的默认值

Dependencies Required

所需依赖

Ensure these packages are installed:
bash
npm install react-hook-form @hookform/resolvers zod
npm install sonner  # for toast notifications
shadcn/ui components needed:
bash
npx shadcn-ui@latest add form button input textarea select checkbox radio-group switch slider
确保已安装以下包:
bash
npm install react-hook-form @hookform/resolvers zod
npm install sonner  # 用于Toast通知
所需的shadcn/ui组件:
bash
npx shadcn-ui@latest add form button input textarea select checkbox radio-group switch slider

Best Practices

最佳实践

  1. Co-locate validation: Keep Zod schemas close to form components
  2. Reuse schemas: Share schemas between client and server validation
  3. Type inference: Use
    z.infer<typeof schema>
    for TypeScript types
  4. Granular validation: Validate on blur for better UX
  5. Optimistic updates: Show success state before server confirmation when appropriate
  6. Error recovery: Allow users to easily fix validation errors
  7. Progress indication: Show loading states during async operations
  8. Data persistence: Consider auto-saving drafts for long forms
  9. Field dependencies: Use form.watch() for conditional fields
  10. Performance: Use mode: 'onBlur' or 'onChange' based on form complexity
  1. 验证逻辑内聚:将Zod Schema与表单组件放在相近位置
  2. 复用Schema:在客户端与服务端验证中共享Schema
  3. 类型推导:使用
    z.infer<typeof schema>
    获取TypeScript类型
  4. 细粒度验证:在失去焦点时触发验证,提升用户体验
  5. 乐观更新:合适时在服务端确认前显示成功状态
  6. 错误恢复:让用户可轻松修复验证错误
  7. 进度提示:异步操作期间显示加载状态
  8. 数据持久化:长表单考虑自动保存草稿
  9. 字段依赖:使用form.watch()处理条件字段
  10. 性能优化:根据表单复杂度选择'onBlur'或'onChange'模式

Troubleshooting

故障排除

Issue: Form not submitting
  • Check handleSubmit is wrapping onSubmit
  • Verify zodResolver is configured
  • Check for validation errors in form state
Issue: Validation not working
  • Ensure schema matches field names exactly
  • Check resolver is zodResolver(schema)
  • Verify field is registered with FormField
Issue: TypeScript errors
  • Use z.infer<typeof schema> for type inference
  • Ensure form values type matches schema type
  • Check FormField generic type matches field value type
Issue: Field not updating
  • Verify field spread {...field} is applied
  • Check value/onChange are not overridden incorrectly
  • Use field.value and field.onChange for controlled components
问题:表单无法提交
  • 检查handleSubmit是否包裹了onSubmit函数
  • 确认已配置zodResolver
  • 检查表单状态中的验证错误
问题:验证不生效
  • 确保Schema与字段名称完全匹配
  • 检查解析器是否为zodResolver(schema)
  • 确认字段已通过FormField注册
问题:TypeScript报错
  • 使用z.infer<typeof schema>进行类型推导
  • 确保表单值类型与Schema类型匹配
  • 检查FormField的泛型类型与字段值类型匹配
问题:字段不更新
  • 确认已应用字段展开{...field}
  • 检查是否错误覆盖了value/onChange
  • 受控组件使用field.value和field.onChange

Additional Resources

额外资源

Consult references/ directory for detailed patterns:
  • references/rhf-patterns.md - Advanced React Hook Form patterns
  • references/zod-validation.md - Complex validation scenarios
  • references/shadcn-form-controls.md - All form component variants
  • references/server-actions.md - Server-side form handling
Use assets/ directory for starting templates:
  • assets/form-template.tsx - Copy and customize
  • assets/field-templates/ - Individual field implementations
  • assets/validation-schemas.ts - Common validation patterns
参考references目录获取详细模式:
  • references/rhf-patterns.md - 高级React Hook Form模式
  • references/zod-validation.md - 复杂验证场景
  • references/shadcn-form-controls.md - 所有表单组件变体
  • references/server-actions.md - 服务端表单处理
使用assets目录的起始模板:
  • assets/form-template.tsx - 可复制后自定义
  • assets/field-templates/ - 各类型字段的实现模板
  • assets/validation-schemas.ts - 通用验证模式