Loading...
Loading...
Full-stack form handling for Inertia Rails: create, edit, delete, multi-step wizard, and file upload forms with validation errors and progress tracking. React examples inline; Vue and Svelte equivalents in references. Use when building any form, handling file uploads, multi-step forms, client-side validation, or wiring form submission to Rails controllers. NEVER react-hook-form. Use `<Form>` for simple forms, useForm for dynamic/programmatic control.
npx skill4agent add inertia-rails/skills inertia-rails-forms<Form><Form>useState<Form>useStateuseForm<Form><Form>useFormrouter.getpreserveStatefetchrouter.patchuseFormreact-hook-formvee-validatesveltekit-superforms<Form>data={...}<Form>datanamedatauseFormuseForm<Form>useFormvalue=defaultValue<Form>isDirtyfalsevalue="1""on"useForm<Form>name<Form>datadata={...}useFormdefaultValue{({ 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>| 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 |
<Form>errorBagonlyresetOnSuccessonBeforeonSuccessonErroronProgressreferences/advanced-forms.mdmethod="patch"defaultValuedefaultCheckeddefaultValue<select>Checkbox without explicitsubmitsvalue— set"on"so Rails casts to boolean correctly.value="1"
<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>transformuseFormtransformuseFormreferences/advanced-forms.mdformRefFormComponentSlotProps<Form>submit()reset()clearErrors()setError()getData()getFormData()validate()touch()defaults()errorsprocessingprogressisDirtyhasErrorswasSuccessfulrecentlySuccessfulimport {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>
</>
)
}useFormuseFormuseFormtransformerrorBagresetOnSuccesssetErrorreferences/advanced-forms.mduseFormadvanced-forms.md<Form><Form>useFormFormDataprogresserrorsprocessingtype 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()useFormsetDatauseStatereferences/file-uploads.mdreferences/vue.md<Form>#defaultuseFormform.emailsetDatav-modelreferences/svelte.md<Form>{#snippet}useForm$form.emailbind:value| 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 | | |
inertia-rails-controllersshadcn-inertiainertia-rails-typescripttype Propsinterfacereferences/file-uploads.md<Form>