inertia-rails-forms
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInertia Rails Forms
Inertia Rails 表单处理
Full-stack form handling for Inertia.js + Rails.
Before building a form, ask:
- Simple create/edit? → component (no state management needed)
<Form> - Requires per-field UI elements? → Still . React
<Form>for UI state (preview URL, file size display) is independent of form data —useStatehandles the submission;<Form>handles the UI.useState - Multi-step wizard, dynamic fields (add/remove inputs), or form data
shared with sibling components (e.g., live preview panel)? → hook
useForm - Tempted by react-hook-form? → Don't. Inertia's already handles CSRF tokens, redirect following, error mapping from Rails, processing state, file upload detection, and history state. react-hook-form would duplicate or fight all of this.
<Form>
When NOT to use or :
<Form>useForm- Data lookups — not a form submission. Use with debounce +
router.get, or rawpreserveStatefor large datasetsfetch - Inline single-field edits without navigation – directly, or
router.patchif you need error display on the fielduseForm
NEVER:
- Use ,
react-hook-form, orvee-validate— Inertiasveltekit-superformsalready handles CSRF, redirect following, error mapping, processing state, and file detection. These libraries fight Inertia's request lifecycle.<Form> - Pass to
data={...}— it has no<Form>prop. Data comes from inputdataattributes automatically.nameis adataconcept.useForm - Use for simple create/edit —
useFormhandles these without state management. Reserve<Form>for multi-step wizards, dynamic add/remove fields, or form data shared with sibling components.useForm - Use controlled instead of
value=on inputs — controlled inputs bypassdefaultValue's dirty tracking, making<Form>alwaysisDirty.false - Omit on checkboxes — without it, the browser submits
value="1"and Rails won't cast to boolean correctly."on" - Call inside a loop or conditional — it's a hook (React rules apply). Create one form instance per logical form.
useForm
Inertia.js + Rails 的全栈表单处理方案。
在构建表单前,请先确认:
- 简单的创建/编辑场景? → 使用组件(无需状态管理)
<Form> - 需要按字段展示UI元素? → 仍然使用组件。 React
<Form>用于处理UI状态(如预览URL、文件大小显示),与表单数据相互独立 ——useState负责处理提交逻辑;<Form>负责UI状态管理。useState - 多步骤向导、动态字段(添加/移除输入框),或表单数据需要与兄弟组件共享(如实时预览面板)? → 使用钩子
useForm - 想要使用react-hook-form? → 请勿使用。Inertia的已经内置了CSRF 令牌处理、提交后重定向、Rails错误映射、请求处理状态、文件上传 检测以及历史状态管理。react-hook-form会重复实现这些功能,甚至与Inertia的请求生命周期冲突。
<Form>
不适合使用或的场景:
<Form>useForm- 数据查询 —— 不属于表单提交场景。对于大数据集,使用带防抖的并设置
router.get,或直接使用preserveStatefetch - 无需跳转的单行内字段编辑 – 直接使用,如果需要在字段上展示错误信息则使用
router.patchuseForm
绝对禁止:
- 使用、
react-hook-form或vee-validate—— Inertiasveltekit-superforms已经内置了CSRF、提交后重定向、错误映射、请求处理状态和文件检测功能。这些库会与Inertia的请求生命周期冲突。<Form> - 给传递
<Form>属性 —— 该组件没有data={...}属性。表单数据会自动从输入框的data属性中获取。name是data的概念。useForm - 简单的创建/编辑场景使用——
useForm无需状态管理即可处理这些场景。仅在多步骤向导、动态添加/移除字段,或表单数据需要与兄弟组件共享时使用<Form>。useForm - 输入框使用受控属性而非
value=—— 受控输入会绕过defaultValue的变更追踪,导致<Form>始终为isDirty。false - 复选框省略—— 如果不设置该属性,浏览器会提交
value="1",Rails无法将其正确转换为布尔值。"on" - 在循环或条件语句中调用—— 这是一个React钩子(需遵循React钩子规则)。每个逻辑表单对应一个表单实例。
useForm
<Form>
Component (Preferred)
<Form><Form>
组件(推荐使用)
<Form>The simplest way to handle forms. Collects data from input attributes
automatically — no manual state management needed. has NO prop —
do NOT pass (that's a concept). For edit forms, use
on inputs.
name<Form>datadata={...}useFormdefaultValueUse render function children to
access form state. Plain children work but give no access to errors,
processing, or progress.
{({ errors, processing }) => (...)}tsx
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>处理表单的最简方式。自动从输入框的属性收集数据
—— 无需手动管理状态。没有属性 ——
请勿传递(这是的概念)。对于编辑表单,在输入框上使用。
name<Form>datadata={...}useFormdefaultValue使用渲染函数作为子元素 来
访问表单状态。普通子元素也可使用,但无法获取错误、
请求处理状态或上传进度。
{({ errors, processing }) => (...)}tsx
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>
)
}
// 普通子元素 —— 合法但无法访问错误/请求处理状态/上传进度:
// <Form method="post" action="/users">
// <input name="name" />
// <button type="submit">Create</button>
// </Form>Delete Form
删除表单
tsx
<Form method="delete" action={`/posts/${post.id}`}>
{({ processing }) => (
<button type="submit" disabled={processing}>
{processing ? 'Deleting...' : 'Delete Post'}
</button>
)}
</Form>tsx
<Form method="delete" action={`/posts/${post.id}`}>
{({ processing }) => (
<button type="submit" disabled={processing}>
{processing ? 'Deleting...' : 'Delete Post'}
</button>
)}
</Form>Key Render Function Properties
渲染函数核心属性
| Property | Type | Purpose |
|---|---|---|
| | Validation errors keyed by field name |
| | True while request is in flight |
| | Upload progress (file uploads only) |
| | True if any errors exist |
| | True after last submit succeeded |
| | True for 2s after success — ideal for "Saved!" feedback |
| | True if any input changed from initial value |
| | Reset specific fields or all fields |
| | Clear specific errors or all errors |
Additional props (, , ,
event callbacks like , , , ) are
documented in — see loading trigger below.
<Form>errorBagonlyresetOnSuccessonBeforeonSuccessonErroronProgressreferences/advanced-forms.md| 属性 | 类型 | 用途 |
|---|---|---|
| | 按字段名索引的验证错误信息 |
| | 请求处理中时为true |
| | 上传进度(仅针对文件上传场景) |
| | 存在任何错误时为true |
| | 上次提交成功后为true |
| | 提交成功后2秒内为true —— 适合用于展示“已保存!”类反馈 |
| | 任何输入框内容与初始值不同时为true |
| | 重置指定字段或所有字段 |
| | 清除指定字段的错误或所有错误 |
更多属性(、、、
事件回调如、、、)的文档见 —— 可查看下方的加载触发说明。
<Form>errorBagonlyresetOnSuccessonBeforeonSuccessonErroronProgressreferences/advanced-forms.mdEdit Form (Pre-populated)
编辑表单(预填充数据)
Use and uncontrolled defaults:
method="patch"- Text/textarea →
defaultValue - Checkbox/radio →
defaultChecked - Select → on
defaultValue<select>
Checkbox without explicitsubmitsvalue— set"on"so Rails casts to boolean correctly.value="1"
tsx
<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>使用和非受控默认值:
method="patch"- 文本框/文本域 →
defaultValue - 复选框/单选框 →
defaultChecked - 下拉选择框 → 在上设置
<select>defaultValue
未显式设置的复选框会提交value—— 设置"on"以便Rails 将其正确转换为布尔值。value="1"
tsx
<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>Transforming Data
数据转换
Use the prop to reshape data before submission without .
transformuseFormFor advanced with , see .
transformuseFormreferences/advanced-forms.md无需使用,直接通过属性在提交前重塑数据。
useFormtransform关于结合的高级用法,见。
useFormtransformreferences/advanced-forms.mdExternal Access with formRef
formRef通过formRef
外部访问
formRefThe ref exposes the same methods and state as render function props ().
Use when you need to interact with the form from outside .
Key ref methods: , , , ,
, , , , .
State: , , , , ,
, .
FormComponentSlotProps<Form>submit()reset()clearErrors()setError()getData()getFormData()validate()touch()defaults()errorsprocessingprogressisDirtyhasErrorswasSuccessfulrecentlySuccessfultsx
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>
</>
)
}该Ref暴露与渲染函数属性相同的方法和状态()。
当需要从外部与表单交互时使用。核心Ref方法:、、、、
、、、、。状态:、、、、、
、。
FormComponentSlotProps<Form>submit()reset()clearErrors()setError()getData()getFormData()validate()touch()defaults()errorsprocessingprogressisDirtyhasErrorswasSuccessfulrecentlySuccessfultsx
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
useFormuseForm
钩子
useFormUse only for multi-step wizards, dynamic add/remove fields, or
form data shared with sibling components.
useFormMANDATORY — READ ENTIRE FILE when using hook, ,
, , multi-step forms, or client-side validation
with :
(~330 lines) — full
API, transform examples, error bag scoping, multi-step wizard
patterns, and client-side validation.
useFormtransformerrorBagresetOnSuccesssetErrorreferences/advanced-forms.mduseFormDo NOT load when using component for simple
create/edit forms — the examples above are sufficient.
advanced-forms.md<Form>仅在多步骤向导、动态添加/移除字段,或
表单数据需要与兄弟组件共享时使用。
useForm**使用钩子、、
、、多步骤表单,或使用进行客户端验证时,**请务必完整阅读以下文档:
(约330行)—— 包含完整的
API、转换示例、错误作用域、多步骤向导模式,以及客户端验证方案。
useFormtransformerrorBagresetOnSuccesssetErrorreferences/advanced-forms.mduseForm如果仅使用组件处理简单的创建/编辑表单,无需查看 —— 上述示例已足够。
<Form>advanced-forms.mdFile Uploads
文件上传
Both and auto-detect files and switch to .
Upload progress is built into the render function — destructure
alongside and :
<Form>useFormFormDataprogresserrorsprocessingtsx
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>
)
}Choosing vs for uploads:
<Form>useForm- File submits with other fields (avatar + name, one Save button) → file input inside
<Form> - Standalone immediate upload (uploads on select, no Save button) → +
<Form>on changeformRef.submit() - Drag-and-drop upload → (dropped files aren't in DOM inputs,
useFormis cleaner)setData
Preview / validation → alongside either approach, see
.
useStatereferences/file-uploads.md<Form>useFormFormDataerrorsprocessingprogresstsx
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>formRef.submit() - 拖拽上传 → 使用(拖拽的文件不在DOM输入框中,
useForm用法更简洁)setData
预览/验证 → 可结合两种方案使用,详见
。
useStatereferences/file-uploads.mdVue / Svelte
Vue / Svelte
All examples above use React syntax. For Vue 3 or Svelte equivalents:
- Vue 3: —
references/vue.mdwith<Form>scoped slot,#defaultreturns reactive proxy (useFormnotform.email),setDatabindingv-model - Svelte: —
references/svelte.mdwith<Form>syntax,{#snippet}returns Writable store (useForm),$form.email, ref exposes methods only (not reactive state)bind:value
MANDATORY — READ THE MATCHING FILE when the project uses Vue or Svelte.
上述所有示例使用React语法。Vue 3或Svelte的等效实现:
- Vue 3:—— 使用
references/vue.md作用域插槽的#default,<Form>返回响应式代理(使用useForm而非form.email),setData绑定v-model - Svelte:—— 使用
references/svelte.md语法的{#snippet},<Form>返回可写存储(useForm),$form.email,Ref仅暴露方法(不包含响应式状态)bind:value
如果项目使用Vue或Svelte,请务必阅读对应的文档。
Troubleshooting
故障排查
| Symptom | Cause | Fix |
|---|---|---|
No access to | Plain children instead of render function | |
| Form sends GET instead of POST | Missing | Add |
| File upload sends empty body | PUT/PATCH with file | Multipart limitation — Inertia auto-adds |
| Errors don't clear after fixing field | Stale error state | Errors auto-clear on next submit; use |
| Using | Controlled inputs ( |
| No file input in form | Progress tracking only activates when |
Checkbox sends | No explicit | Add |
| Form submits twice in dev | React StrictMode double-invocation | Normal in development — StrictMode remounts components. Only fires once in production |
Used | | |
| 症状 | 原因 | 修复方案 |
|---|---|---|
无法访问 | 使用普通子元素而非渲染函数 | |
| 表单发送GET请求而非POST | 缺少 | 添加 |
| 文件上传发送空请求体 | 使用PUT/PATCH方法上传文件 | 多部分表单限制 —— Inertia会自动添加 |
| 修复字段后错误信息未清除 | 错误状态过期 | 错误会在下次提交时自动清除;如需立即清除,使用 |
| 使用 | 受控输入( |
| 表单中没有文件输入框 | 仅当 |
复选框提交 | 未显式设置 | 给复选框添加 |
| 开发环境中表单提交两次 | React StrictMode双重调用 | 开发环境下的正常现象 —— StrictMode会重新挂载组件。生产环境中只会触发一次 |
文件上传带预览的场景使用了 | 误将 | 使用 |
Related Skills
相关技能
- Server-side PRG & errors → (redirect_back, to_hash, flash)
inertia-rails-controllers - shadcn inputs → (Input/Select adaptation, toast UI)
shadcn-inertia - Page props typing → (
inertia-rails-typescriptnottype Props, TS2344)interface
MANDATORY — READ ENTIRE FILE when handling file uploads with image preview,
Active Storage, or direct uploads:
(~200 lines) — image preview
with , Active Storage integration, direct upload setup, multiple files,
and progress tracking.
references/file-uploads.md<Form>- 服务端PRG与错误处理 → (redirect_back, to_hash, flash)
inertia-rails-controllers - shadcn 输入组件 → (输入框/选择框适配、提示框UI)
shadcn-inertia - 页面属性类型定义 → (使用
inertia-rails-typescript而非type Props,解决TS2344错误)interface
如果处理带图片预览、Active Storage或直接上传的文件上传场景,请务必完整阅读以下文档:
(约200行)—— 包含结合的图片预览、Active Storage集成、直接上传配置、多文件上传,以及进度追踪方案。
references/file-uploads.md<Form>