inertia-rails-forms について
Inertia.js と Rails のフォーム開発を、React、Vue、Svelte 向けに案内するスキルです。Form コンポーネントと useForm の使い分けに加え、検証、ファイルアップロード、よくある問題を説明します。
主な機能
- 01Form と useForm の使い分け
- 02Rails の検証エラー処理
- 03ファイルアップロードと進捗表示
- 04フレームワーク別のガイド
活用シーン
- 01シンプルな作成・編集フォームの構築
- 02複数ステップや動的フォームの実装
- 03進捗付きファイルアップロードの追加
- 04フォーム状態や送信問題の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ inertia-rails/ skills --skill inertia-rails-formsインストール後、Claude Code で/inertia-rails-formsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced-forms.mdfile-uploads.mdsvelte.mdvue.md詳細
Inertia Rails フォーム
Inertia.js + Rails のフルスタックフォーム処理です。
フォームを作成する前に確認してください:
- シンプルな作成/編集ですか? →
<Form>コンポーネント(状態管理は不要です) - フィールドごとの UI 要素が必要ですか? → それでも
<Form>を使います。 React のuseStateによる UI 状態(プレビュー URL、ファイルサイズ表示など)はフォームデータとは独立しています。<Form>が送信を処理し、useStateが UI を処理します。 - 複数ステップのウィザード、動的フィールド(入力欄の追加/削除)、または兄弟コンポーネントと共有するフォームデータ(ライブプレビューパネルなど)が必要ですか? →
useFormフック - 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は、複数ステップのウィザード、動的な入力欄の追加/削除、または兄弟コンポーネントと共有するフォームデータに使用してください。 - 入力欄で
defaultValueの代わりに制御されたvalue=を使う — 制御された入力欄は<Form>の変更追跡を迂回するため、isDirtyが常にfalseになります。 - チェックボックスの
value="1"を省略する — 省略すると、ブラウザーは"on"を送信し、Rails はブール値として正しくキャストできません。 - ループや条件分岐の中で
useFormを呼び出す — これはフックです(React のルールが適用されます)。論理上のフォームごとにフォームインスタンスを 1 つ作成してください。
<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>
レンダー関数の主なプロパティ
| プロパティ | 型 | 目的 |
|---|---|---|
errors | Record<string, string> | フィールド名をキーとするバリデーションエラー |
processing | boolean | リクエストの処理中は true |
progress | { percentage: number } | null | アップロードの進行状況(ファイルアップロードのみ) |
hasErrors | boolean | エラーが存在する場合は true |
wasSuccessful | boolean | 直近の送信が成功した後は true |
recentlySuccessful | boolean | 成功後 2 秒間 true — 「保存しました!」のフィードバックに最適 |
isDirty | boolean | いずれかの入力欄が初期値から変更されている場合は 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"を送信します。Rails がブール値として正しくキャストできるよう、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>
データの変換
useForm を使わずに送信前のデータを整形するには、transform prop を使用します。
useForm での高度な transform については、references/advanced-forms.md を参照してください。
formRef による外部からのアクセス
ref には、レンダー関数の props(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 フック
useForm は、複数ステップのウィザード、動的なフィールドの追加/削除、または兄弟コンポーネント間でフォームデータを共有する場合にのみ使用してください。
必須 — ファイル全体を読むこと:useForm フック、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 のどちらを使うか:
- ファイルを他のフィールドと一緒に送信する場合(アバター+名前、保存ボタンは1つ)→
<Form>内にファイル入力を配置 - 単独の即時アップロードの場合(選択時にアップロードし、保存ボタンはない)→
<Form>と、変更時に呼び出すformRef.submit() - ドラッグ&ドロップでアップロードする場合→
useForm(ドロップしたファイルは DOM 入力に含まれないため、setDataのほうが簡潔です)
プレビュー/バリデーションには、どちらの方法でも useState を併用してください。詳しくは
references/file-uploads.md を参照してください。
Vue/Svelte
上記の例はすべて React の構文を使用しています。Vue 3 または Svelte での同等の方法については、次を参照してください。
- Vue 3:
references/vue.md—#defaultのスコープ付きスロットを使った<Form>、リアクティブなプロキシを返すuseForm(setDataではなくform.email)、v-modelによるバインディング - Svelte:
references/svelte.md—{#snippet}構文を使った<Form>、Writable ストアを返すuseForm($form.email)、bind:value、メソッドのみを公開しリアクティブな状態は公開しない ref
プロジェクトで Vue または Svelte を使用している場合は、必須 — 該当するファイルを読むこと。
トラブルシューティング
| 症状 | 原因 | 対処法 |
|---|---|---|
errors/processing にアクセスできない | レンダー関数ではなく通常の子要素を使用している | <Form> の子要素は {({ errors, processing }) => (...)} にしてください |
| フォームが POST ではなく GET を送信する | method prop がない | method="post"(または "patch"、"delete")を追加してください |
| ファイルのアップロードで空の本文が送信される | ファイルを含む PUT/PATCH | マルチパートの制限 — Inertia は POST に変換するため、_method フィールドを自動追加します |
| フィールドを修正してもエラーが消えない | 古いエラー状態 | エラーは次回の送信時に自動で消去されます。すぐに消去するには clearErrors('field') を使用してください |
isDirty が常に false | defaultValue ではなく value を使用している | 制御入力(value=)は変更追跡を回避します。defaultValue を使用してください |
progress が常に null | フォームにファイル入力がない | 進行状況の追跡は、<Form> がファイル入力を検出した場合にのみ有効になります |
チェックボックスが "on" を送信する | 明示的な value がない | チェックボックスの入力に value="1" を追加してください |
| 開発時にフォームが2回送信される | React StrictMode による二重呼び出し | 開発環境では正常です。StrictMode はコンポーネントを再マウントします。本番環境では1回だけ実行されます |
プレビュー付きのファイルアップロードに useForm を使用した | onChange と useState を「プログラムによるデータ操作」と誤解している | プレビュー UI には <Form> と useState を使用してください。useForm が必要なのは、フォームの送信データを React の状態として保持する必要がある場合(複数ステップ、動的なフィールドの追加/削除)に限られます。ファイルのプレビューはローカル UI の状態であり、フォームデータではありません |
関連するスキル
- サーバー側の PRG とエラー →
inertia-rails-controllers(redirect_back、to_hash、flash) - shadcn の入力欄 →
shadcn-inertia(Input/Select の適応、トースト UI) - ページプロパティの型指定 →
inertia-rails-typescript(interfaceではなくtype Props、TS2344)
必須 — ファイル全体を読むこと:画像プレビュー付きのファイルアップロード、Active Storage、またはダイレクトアップロードを扱う場合:
references/file-uploads.md(約 200 行) — <Form> を使用した画像プレビュー、Active Storage の統合、ダイレクトアップロードの設定、複数ファイル、進行状況の追跡。