Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问clear审计未展示

form-state-patterns形成状态模式

Agent Skill

form-state-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

428

周安装

18

GitHub Stars

公开资料未说明

下载量

150
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:form-state-patterns(形成状态模式)
来源仓库:https://github.com/yonatangross/skillforge-claude-plugin
仓库路径:skills/form-state-patterns
安装命令:
npx skills add yonatangross/skillforge-claude-plugin --skill "form-state-patterns"
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

AgentSkills.tonpx skills
npx skills add yonatangross/skillforge-claude-plugin --skill "form-state-patterns"

简介

form-state-patterns 用于查找与检索表单状态管理相关的技术方案。

  • 适用于 React、Vue 等前端框架中表单逻辑组织与验证优化。
  • 可通过关键词匹配最佳实践或代码模板,辅助开发者快速实现。
  • 使用前应确认项目技术栈,避免引入不兼容的状态管理库。
  • 建议结合具体业务场景选择模式,如受控组件、Formik 或 Zod 集成。

SKILL.md

Form State Patterns

Production form patterns with React Hook Form v7 + Zod - type-safe, performant, accessible.

Overview

  • Complex forms with validation
  • Multi-step wizards
  • Dynamic field arrays
  • Server-side validation
  • Async field validation
  • Forms with file uploads

Core Patterns

1. Basic Form with Zod Schema

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Min 8 characters'),
  confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords don't match",
  path: ['confirmPassword'],
});

type UserForm = z.infer<typeof userSchema>;

function SignupForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<UserForm>({
    resolver: zodResolver(userSchema),
    defaultValues: { email: '', password: '', confirmPassword: '' },
  });

  const onSubmit = async (data: UserForm) => {
    await api.signup(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} aria-invalid={!!errors.email} />
      {errors.email && <span role="alert">{errors.email.message}</span>}

      <input type="password" {...register('password')} />
      {errors.password && <span role="alert">{errors.password.message}</span>}

      <input type="password" {...register('confirmPassword')} />
      {errors.confirmPassword && <span role="alert">{errors.confirmPassword.message}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Sign Up'}
      </button>
    </form>
  );
}

2. Field Arrays (Dynamic Fields)

import { useFieldArray, useForm } from 'react-hook-form';

const orderSchema = z.object({
  items: z.array(z.object({
    productId: z.string().min(1),
    quantity: z.number().min(1).max(100),
  })).min(1, 'At least one item required'),
});

function OrderForm() {
  const { control, register, handleSubmit } = useForm({
    resolver: zodResolver(orderSchema),
    defaultValues: { items: [{ productId: '', quantity: 1 }] },
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'items',
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input {...register(`items.${index}.productId`)} />
          <input
            type="number"
            {...register(`items.${index}.quantity`, { valueAsNumber: true })}
          />
          <button type="button" onClick={() => remove(index)}>Remove</button>
        </div>
      ))}
      <button type="button" onClick={() => append({ productId: '', quantity: 1 })}>
        Add Item
      </button>
      <button type="submit">Submit Order</button>
    </form>
  );
}

3. Async Field Validation

const usernameSchema = z.object({
  username: z.string()
    .min(3)
    .refine(async (value) => {
      const available = await checkUsernameAvailability(value);
      return available;
    }, 'Username already taken'),
});

// Or with mode: 'onBlur' for better UX
const { register } = useForm({
  resolver: zodResolver(usernameSchema),
  mode: 'onBlur', // Validate on blur, not on every keystroke
});

4. Server Actions (React 19 / Next.js)

// actions.ts
'use server';
import { z } from 'zod';

const contactSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  message: z.string().min(10),
});

export async function submitContact(formData: FormData) {
  const result = contactSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
  });

  if (!result.success) {
    return { errors: result.error.flatten().fieldErrors };
  }

  await saveContact(result.data);
  return { success: true };
}

// Component
'use client';
import { useActionState } from 'react';
import { submitContact } from './actions';

function ContactForm() {
  const [state, formAction, isPending] = useActionState(submitContact, null);

  return (
    <form action={formAction}>
      <input name="name" />
      {state?.errors?.name && <span>{state.errors.name[0]}</span>}

      <input name="email" />
      {state?.errors?.email && <span>{state.errors.email[0]}</span>}

      <textarea name="message" />
      {state?.errors?.message && <span>{state.errors.message[0]}</span>}

      <button type="submit" disabled={isPending}>
        {isPending ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
}

5. Multi-Step Wizard

const steps = ['personal', 'address', 'payment'] as const;

const wizardSchema = z.object({
  personal: z.object({
    firstName: z.string().min(1),
    lastName: z.string().min(1),
  }),
  address: z.object({
    street: z.string().min(1),
    city: z.string().min(1),
  }),
  payment: z.object({
    cardNumber: z.string().length(16),
  }),
});

function WizardForm() {
  const [step, setStep] = useState(0);
  const methods = useForm({
    resolver: zodResolver(wizardSchema),
    mode: 'onTouched',
  });

  const nextStep = async () => {
    const stepKey = steps[step];
    const isValid = await methods.trigger(stepKey);
    if (isValid) setStep((s) => Math.min(s + 1, steps.length - 1));
  };

  return (
    <FormProvider {...methods}>
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        {step === 0 && <PersonalStep />}
        {step === 1 && <AddressStep />}
        {step === 2 && <PaymentStep />}

        <div>
          {step > 0 && <button type="button" onClick={() => setStep(s => s - 1)}>Back</button>}
          {step < steps.length - 1 && <button type="button" onClick={nextStep}>Next</button>}
          {step === steps.length - 1 && <button type="submit">Submit</button>}
        </div>
      </form>
    </FormProvider>
  );
}

6. File Upload with Preview

const fileSchema = z.object({
  avatar: z
    .instanceof(FileList)
    .refine((files) => files.length === 1, 'File required')
    .refine((files) => files[0]?.size <= 5_000_000, 'Max 5MB')
    .refine(
      (files) => ['image/jpeg', 'image/png'].includes(files[0]?.type),
      'Only JPEG/PNG'
    ),
});

function AvatarUpload() {
  const [preview, setPreview] = useState<string | null>(null);
  const { register, watch } = useForm({ resolver: zodResolver(fileSchema) });

  const avatar = watch('avatar');
  useEffect(() => {
    if (avatar?.[0]) {
      setPreview(URL.createObjectURL(avatar[0]));
    }
  }, [avatar]);

  return (
    <>
      {preview && <img src={preview} alt="Preview" />}
      <input type="file" accept="image/*" {...register('avatar')} />
    </>
  );
}

7. Controlled Components Integration

import { Controller } from 'react-hook-form';
import { DatePicker } from '@/components/ui/date-picker';

function EventForm() {
  const { control } = useForm();

  return (
    <Controller
      name="eventDate"
      control={control}
      render={({ field, fieldState }) => (
        <DatePicker
          value={field.value}
          onChange={field.onChange}
          onBlur={field.onBlur}
          error={fieldState.error?.message}
        />
      )}
    />
  );
}

Performance Optimizations

// Isolate re-renders with Controller
<Controller name="email" control={control} render={...} />

// Use mode: 'onBlur' instead of 'onChange'
useForm({ mode: 'onBlur' });

// Avoid watching entire form
const email = watch('email'); // Good: specific field
const form = watch(); // Bad: entire form triggers re-render

Accessibility Checklist

  • All inputs have associated labels
  • Error messages use role="alert"
  • Invalid inputs have aria-invalid="true"
  • Submit button shows loading state
  • Focus management on error

Quick Reference

// ✅ Basic form setup with Zod resolver
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
  resolver: zodResolver(schema),
  defaultValues: { name: '', email: '' },
  mode: 'onBlur', // Validate on blur, not every keystroke
});

// ✅ Register inputs with accessibility
<input
  {...register('email')}
  aria-invalid={!!errors.email}
  aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && <p id="email-error" role="alert">{errors.email.message}</p>}

// ✅ Controller for third-party components
<Controller
  name="date"
  control={control}
  render={({ field, fieldState }) => (
    <DatePicker value={field.value} onChange={field.onChange} error={fieldState.error} />
  )}
/>

// ✅ useActionState for React 19 Server Actions
const [state, formAction, isPending] = useActionState(serverAction, initialState);

// ❌ NEVER watch entire form (causes full re-render)
const allValues = watch(); // BAD

// ❌ NEVER use index as key in field arrays
fields.map((field, index) => <div key={index}>...</div>) // BAD - use field.id

Key Decisions

DecisionOption AOption BRecommendation
Validation libraryYupZodZod - better TypeScript inference, smaller bundle
Validation modeonChangeonBluronBlur - better performance, less noise
Complex componentsregisterControllerController - for non-native inputs
Server validationClient-onlyServer ActionsServer Actions - for mutations with React 19
Form state libFormikReact Hook FormRHF - better performance, less re-renders
Field arraysManual stateuseFieldArrayuseFieldArray - built-in add/remove/swap

Anti-Patterns (FORBIDDEN)

// ❌ FORBIDDEN: Watching entire form
const form = watch();  // Re-renders on EVERY change to ANY field

// ❌ FORBIDDEN: Using index as key in field arrays
{fields.map((field, index) => (
  <div key={index}>  // WRONG - will cause bugs on reorder/remove
    <input {...register(`items.${index}.name`)} />
  </div>
))}
// ✅ CORRECT: Use field.id
{fields.map((field, index) => (
  <div key={field.id}>
    <input {...register(`items.${index}.name`)} />
  </div>
))}

// ❌ FORBIDDEN: Missing defaultValues for all fields
useForm({
  resolver: zodResolver(schema),
  // Missing defaultValues causes uncontrolled->controlled warning
});

// ❌ FORBIDDEN: Using native validation with Zod
<input type="email" required {...register('email')} /> // Conflicts with Zod
// ✅ CORRECT: Disable native validation
<form onSubmit={handleSubmit(onSubmit)} noValidate>

// ❌ FORBIDDEN: setError without manual clearErrors
const onSubmit = async (data) => {
  const result = await api.submit(data);
  if (!result.success) {
    setError('email', { message: 'Email taken' });
    // Missing clearErrors on next submit!
  }
};

// ❌ FORBIDDEN: Async validation on every keystroke
const schema = z.object({
  username: z.string().refine(async (val) => {
    return await checkAvailable(val);  // Fires on every character!
  }),
});
// ✅ CORRECT: Use mode: 'onBlur' or debounce
useForm({ mode: 'onBlur' });

// ❌ FORBIDDEN: Missing error messages in Zod
const schema = z.object({
  email: z.string().email(),  // Generic "Invalid" error
});
// ✅ CORRECT: Custom error messages
const schema = z.object({
  email: z.string().email('Please enter a valid email address'),
});

Related Skills

  • tanstack-query-advanced - Combine form mutations with TanStack Query
  • zustand-patterns - Form wizard state with multi-step persistence
  • input-validation - Server-side validation and sanitization
  • accessibility-specialist - WCAG compliance for forms

Capability Details

zod-validation

Keywords: zod, schema, validation, refine, transform, parse Solves: Type-safe validation with automatic TypeScript inference

field-arrays

Keywords: useFieldArray, dynamic, add, remove, append, swap, move Solves: Dynamic forms with add/remove items like invoices, surveys

server-actions

Keywords: useActionState, Server Actions, 'use server', formData Solves: React 19 progressive enhancement with server-side validation

multi-step-wizard

Keywords: wizard, steps, trigger, FormProvider, partial validation Solves: Complex multi-page forms with step-by-step validation

async-validation

Keywords: async, refine, debounce, username, availability Solves: Server-side validation during input (e.g., username availability)

file-upload

Keywords: FileList, File, upload, preview, drag-drop, validation Solves: File input validation with size, type, and preview handling

References

  • references/validation-patterns.md - Advanced Zod patterns
  • scripts/form-template.tsx - Production form template
  • checklists/form-checklist.md - Implementation checklist
  • examples/form-examples.md - Real-world form examples

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

27.86%
按下载量换算42

OpenCode

25.3%
按下载量换算38

Antigravity

20.21%
按下载量换算30

Gemini CLI

12.28%
按下载量换算18

windsurf

7.21%
按下载量换算11

trae

3.46%
按下载量换算5

安全审计

暂无安全审计结果可展示。

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills