mcpField
I
inertia-rails-forms

inertia-rails-forms

github.com/inertia-rails开发70488 次安装37 周前更新
代码仓库

关于 inertia-rails-forms

面向 Inertia.js 与 Rails 表单开发的技能指南,覆盖 React、Vue 和 Svelte。说明何时使用 Form 组件或 useForm,并介绍验证、文件上传及常见问题。

主要功能

  1. 01选择 Form 或 useForm
  2. 02处理 Rails 验证错误
  3. 03文件上传与进度显示
  4. 04框架专属指导

适用场景

  1. 01构建简单的创建和编辑表单
  2. 02实现多步骤或动态表单
  3. 03添加带进度的文件上传
  4. 04排查表单状态与提交问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/inertia-rails/skills --skill inertia-rails-forms

安装后在 Claude Code 中输入/inertia-rails-forms确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-forms.md
file-uploads.md
svelte.md
vue.md

详细介绍

Inertia Rails 表单

适用于 Inertia.js + Rails 的全栈表单处理。

创建表单前,先确认:

  • 简单的创建/编辑? → 使用 <Form> 组件(无需状态管理)
  • 需要为每个字段设置 UI 元素? → 仍使用 <Form>。 React useState 用于 UI 状态(预览 URL、文件大小显示),与表单数据相互独立——<Form> 处理提交;useState 处理 UI。
  • 多步向导、动态字段(添加/移除输入项),或需要与同级组件共享表单数据(例如实时预览面板)? → 使用 useForm hook
  • 考虑使用 react-hook-form? → 不要这样做。Inertia 的 <Form> 已处理 CSRF 令牌、重定向跟进、Rails 错误映射、处理中状态、文件上传检测和历史状态。react-hook-form 会重复或干扰这些功能。

以下情况不要使用 <Form> 或 useForm:

  • 数据查询——这不是表单提交。使用带防抖和 preserveState 的 router.get,或对大型数据集使用原始 fetch
  • 无需导航的单字段内联编辑——直接使用 router.patch;如果需要显示字段错误,则使用 useForm

绝对不要:

  • 使用 react-hook-form、vee-validate 或 sveltekit-superforms——Inertia <Form> 已处理 CSRF、重定向跟进、错误映射、处理中状态和文件检测。这些库会干扰 Inertia 的请求生命周期。
  • 向 <Form> 传递 data={...}——它没有 data 属性。数据会自动从输入项的 name 属性获取。data 是 useForm 的概念。
  • 对简单的创建/编辑使用 useForm——<Form> 无需状态管理即可处理这些场景。仅在多步向导、动态添加/移除字段,或需要与同级组件共享表单数据时使用 useForm。
  • 在输入项上使用受控的 value=,而不是 defaultValue——受控输入会绕过 <Form> 的脏状态跟踪,导致 isDirty 始终为 false。
  • 在复选框上省略 value="1"——否则浏览器会提交 "on",Rails 无法正确转换为布尔值。
  • 在循环或条件语句中调用 useForm——它是一个 hook(需遵循 React 规则)。每个逻辑表单创建一个表单实例。

<Form> 组件(首选)

处理表单最简单的方式。会自动从输入项的 name 属性收集数据——无需手动管理状态。<Form> 没有 data 属性——不要传递 data={...}(这是 useForm 的概念)。对于编辑表单,在输入项上使用 defaultValue。

使用渲染函数子项 {({ errors, processing }) => (...)} 访问表单状态。普通子项也能使用,但无法访问错误、处理中状态或进度。

import { Form } from '@inertiajs/react'

export default function CreateUser() {
  return (
    <Form method="post" action="/users">
      {({ errors, processing }) => (
        <>
          <input type="text" name="name" />
          {errors.name && <span className="error">{errors.name}</span>}

          <input type="email" name="email" />
          {errors.email && <span className="error">{errors.email}</span>}

          <button type="submit" disabled={processing}>
            {processing ? 'Creating...' : 'Create User'}
          </button>
        </>
      )}
    </Form>
  )
}

// Plain children — valid but no access to errors/processing/progress:
// <Form method="post" action="/users">
//   <input name="name" />
//   <button type="submit">Create</button>
// </Form>
删除表单
<Form method="delete" action={`/posts/${post.id}`}>
  {({ processing }) => (
    <button type="submit" disabled={processing}>
      {processing ? 'Deleting...' : 'Delete Post'}
    </button>
  )}
</Form>
渲染函数的关键属性
属性类型用途
errorsRecord<string, string>按字段名称索引的验证错误
processingboolean请求进行中时为 true
progress{ percentage: number } | null上传进度(仅限文件上传)
hasErrorsboolean存在任何错误时为 true
wasSuccessfulboolean上次提交成功后为 true
recentlySuccessfulboolean成功后 2 秒内为 true,适合显示“已保存!”提示
isDirtyboolean任一输入项的值与初始值不同时为 true
reset(...fields) => void重置指定字段或所有字段
clearErrors(...fields) => void清除指定错误或所有错误

其他 <Form> 属性(errorBag、only、resetOnSuccess,以及 onBefore、onSuccess、onError、onProgress 等事件回调)见 references/advanced-forms.md——请参阅下方的加载触发器。

编辑表单(预填充)

使用 method="patch" 和非受控默认值:

  • 文本框/文本区域 → defaultValue
  • 复选框/单选按钮 → defaultChecked
  • 选择框 → 在 <select> 上设置 defaultValue

未显式设置 value 的复选框会提交 "on"——设置 value="1",以便 Rails 正确转换为布尔值。

<Form method="patch" action={`/posts/${post.id}`}>
  {({ errors, processing }) => (
    <>
      <input type="text" name="title" defaultValue={post.title} />
      {errors.title && <span className="error">{errors.title}</span>}

      <label>
        <input type="checkbox" name="published" value="1"
          defaultChecked={post.published} />
        Published
      </label>

      <button type="submit" disabled={processing}>
        {processing ? 'Saving...' : 'Update Post'}
      </button>
    </>
  )}
</Form>
转换数据

使用 transform 属性可在提交前重塑数据,无需使用 useForm。

有关 useForm 中高级 transform 用法,请参阅 references/advanced-forms.md。

使用 formRef 进行外部访问

该 ref 会公开与渲染函数属性(FormComponentSlotProps)相同的方法和状态。 当你需要在 <Form> 外部与表单交互时使用。 主要 ref 方法:submit()、reset()、clearErrors()、setError()、 getData()、getFormData()、validate()、touch()、defaults()。 状态:errors、processing、progress、isDirty、hasErrors、 wasSuccessful、recentlySuccessful。

import {useRef} from 'react'
import {Form} from '@inertiajs/react'
import type {FormComponentRef} from '@inertiajs/core'

export default function CreateUser() {
  const formRef = useRef<FormComponentRef>(null)

  return (
    <>
      <Form ref={formRef} method='post' action='/users'>
        {({errors}) => (
          <>
            <input type='text' name='name'/>
            {errors.name && <span className='error'>{errors.name}</span>}
          </>
        )}
      </Form>
      <button onClick={() => formRef.current?.submit()}>Submit</button>
      <button onClick={() => formRef.current?.reset()}>Reset</button>
    </>
  )
}

useForm Hook

仅在多步骤向导、动态添加/移除字段,或需要与同级组件共享表单数据时使用 useForm。

必须 — 使用 useForm Hook、transform、errorBag、resetOnSuccess、多步骤表单,或使用 setError 进行客户端验证时,请阅读整个文件: references/advanced-forms.md(约 330 行)——完整的 useForm API、transform 示例、错误包作用域、多步骤向导 模式,以及客户端验证。

使用 <Form> 组件创建简单的新增/编辑表单时,请勿加载 advanced-forms.md——上面的示例已足够。

文件上传

<Form> 和 useForm 都会自动检测文件并切换到 FormData。 上传进度已内置于渲染函数中——与 errors 和 processing 一起解构 progress:

type Props = { user: User }

export default function EditProfile({ user }: Props) {
  return (
    <Form method="patch" action="/profile">
      {({ errors, processing, progress }) => (
        <>
          <input type="text" name="name" defaultValue={user.name} />
          {errors.name && <span className="error">{errors.name}</span>}

          <input type="file" name="avatar" />
          {errors.avatar && <span className="error">{errors.avatar}</span>}

          {progress && (
            <progress value={progress.percentage ?? 0} max="100" />
          )}

          <button type="submit" disabled={processing}>
            {processing ? 'Uploading...' : 'Save'}
          </button>
        </>
      )}
    </Form>
  )
}

选择用于上传的 <Form> 或 useForm:

  • 文件与其他字段一起提交(头像 + 姓名,一个“保存”按钮)→ 在 <Form> 内放置文件输入框
  • 独立的即时上传(选择后立即上传,无“保存”按钮)→ 使用 <Form> + 在 change 时调用 formRef.submit()
  • 拖放上传 → 使用 useForm(拖放的文件不在 DOM 输入框中,使用 setData 更简洁)

预览/验证 → 在任一方案中配合使用 useState,请参阅 references/file-uploads.md。

Vue / Svelte

以上示例均使用 React 语法。Vue 3 或 Svelte 的等效用法:

  • Vue 3:references/vue.md — <Form> 使用 #default 作用域插槽,useForm 返回响应式代理(使用 form.email 而非 setData),v-model 绑定
  • Svelte:references/svelte.md — <Form> 使用 {#snippet} 语法,useForm 返回 Writable store($form.email),bind:value,ref 仅公开方法(不包括响应式状态)

必须 — 项目使用 Vue 或 Svelte 时,请阅读对应文件。

故障排除

症状原因解决方法
无法访问 errors/processing使用了普通子元素,而非渲染函数<Form> 子元素应为 {({ errors, processing }) => (...)}
表单发送 GET 而非 POST缺少 method 属性添加 method="post"(或 "patch"、"delete")
文件上传发送空请求体使用 PUT/PATCH 上传文件多部分表单限制——Inertia 会自动添加 _method 字段,将请求转换为 POST
修正字段后错误未清除错误状态过期错误会在下次提交时自动清除;如需立即清除,请使用 clearErrors('field')
isDirty 始终为 false使用了 value 而非 defaultValue受控输入框(value=)会绕过脏状态跟踪——请使用 defaultValue
progress 始终为 null表单中没有文件输入框只有当 <Form> 检测到文件输入框时,才会启用进度跟踪
复选框发送 "on"未显式设置 value为复选框输入框添加 value="1"
开发环境中表单提交两次React StrictMode 双重调用开发环境中的正常现象——StrictMode 会重新挂载组件。生产环境中只会触发一次
使用 useForm 上传文件并预览将 onChange + useState 误认为“程序化数据操作”使用 <Form> + useState 实现预览 UI。只有当表单提交数据必须存储在 React 状态中(多步骤、动态添加/移除字段)时,才需要 useForm。文件预览属于本地 UI 状态,而非表单数据

相关技能

  • 服务端 PRG 与错误处理 → inertia-rails-controllers(redirect_back、to_hash、flash)
  • shadcn 输入控件 → shadcn-inertia(Input/Select 适配、toast UI)
  • 页面 props 类型 → inertia-rails-typescript(使用 type Props 而非 interface,TS2344)

必须 — 处理带图片预览的文件上传、Active Storage 或直接上传时,请阅读整个文件: references/file-uploads.md(约 200 行)——使用 <Form> 进行图片预览、Active Storage 集成、直接上传设置、多文件和进度跟踪。

评论

支持 Markdown