form-generator-rhf-zod
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseForm 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/formsscripts/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/schemasscripts/generate_form.py - 根据字段规格生成表单组件、Zod Schema和服务端操作函数。
使用方式:
bash
python scripts/generate_form.py --name CharacterForm --fields fields.json --output components/formsscripts/generate_zod_schema.py - 将字段规格转换为带有验证规则的Zod Schema。
使用方式:
bash
python scripts/generate_zod_schema.py --fields fields.json --output lib/schemasReferences
参考文档
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.tsGenerated 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/formsGenerated 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:
- Proper Labels: Every form control must have an associated FormLabel
- Error Messages: Use FormMessage to announce validation errors
- Descriptions: Use FormDescription for helpful context
- Required Fields: Mark required fields visually and in ARIA attributes
- Focus Management: Ensure logical tab order and focus indicators
- Keyboard Navigation: All controls operable via keyboard
- ARIA Attributes: FormField automatically sets aria-describedby and aria-invalid
- Error Summary: Consider adding error summary at top of form for screen readers
确保表单具备可访问性,需做到:
- 规范标签:每个表单控件必须关联对应的FormLabel
- 错误提示:使用FormMessage播报验证错误
- 辅助说明:使用FormDescription提供上下文帮助
- 必填标识:视觉上和ARIA属性中标记必填字段
- 焦点管理:确保合理的Tab切换顺序和焦点指示器
- 键盘导航:所有控件支持键盘操作
- ARIA属性:FormField自动设置aria-describedby和aria-invalid属性
- 错误汇总:考虑在表单顶部添加错误汇总,方便屏幕阅读器用户查看
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 notificationsshadcn/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 sliderBest Practices
最佳实践
- Co-locate validation: Keep Zod schemas close to form components
- Reuse schemas: Share schemas between client and server validation
- Type inference: Use for TypeScript types
z.infer<typeof schema> - Granular validation: Validate on blur for better UX
- Optimistic updates: Show success state before server confirmation when appropriate
- Error recovery: Allow users to easily fix validation errors
- Progress indication: Show loading states during async operations
- Data persistence: Consider auto-saving drafts for long forms
- Field dependencies: Use form.watch() for conditional fields
- Performance: Use mode: 'onBlur' or 'onChange' based on form complexity
- 验证逻辑内聚:将Zod Schema与表单组件放在相近位置
- 复用Schema:在客户端与服务端验证中共享Schema
- 类型推导:使用获取TypeScript类型
z.infer<typeof schema> - 细粒度验证:在失去焦点时触发验证,提升用户体验
- 乐观更新:合适时在服务端确认前显示成功状态
- 错误恢复:让用户可轻松修复验证错误
- 进度提示:异步操作期间显示加载状态
- 数据持久化:长表单考虑自动保存草稿
- 字段依赖:使用form.watch()处理条件字段
- 性能优化:根据表单复杂度选择'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 - 通用验证模式