remix-v2-forms-review について
Remix v2 のフォームをレビューするスキルです。送信方法、処理中状態、アップロード、入力検証、複数アクションのルート構成を確認し、状況別ルールと検証手順で誤検出を抑えます。
主な機能
- 01Form と fetcher の使用確認
- 02処理中状態のレビュー
- 03アップロードと FormData の検証確認
- 04複数アクションのルート構成確認
- 05根拠と例外の検証手順
活用シーン
- 01Remix フォーム変更のレビュー
- 02ルート action を迂回する変更の検出
- 03行ごとの処理中表示の確認
- 04アップロードと入力検証の監査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ existential-birds/ beagle --skill remix-v2-forms-reviewインストール後、Claude Code で/remix-v2-forms-reviewを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/form-vs-fetcher.mdmulti-action-routes.mdpending-state.mduploads-validation.md詳細
Remix v2 フォームのコードレビュー
標準的なパターンについては、remix-v2-forms を参照してください。このスキルでは違反を指摘し、姉妹スキルではパターンを説明します。
クイックリファレンス
| 問題の種類 | 参照先 |
|---|---|
手動の fetch()、ネイティブの <form>、<Form> と useFetcher の誤った選択 | references/form-vs-fetcher.md |
useState による読み込みフラグ、行単位の処理に useNavigation を使用、保留中の状態の欠落 | references/pending-state.md |
メモリ使用量に上限のないアップロード、encType の欠落、検証されていない FormData、ミラーリングされた楽観的状態 | references/uploads-validation.md |
| インテントパターンを使わずルートが増え続ける、PE フォールバックなしの PUT/DELETE | references/multi-action-routes.md |
レビューチェックリスト
- アプリ内の変更には
<Form>または<fetcher.Form>を使用し、fetch()/axiosは使用しない - POST による変更では、ネイティブの
<form>ではなく@remix-run/reactから<Form>をインポートする - URL を変更しない場合(行の切り替え、インライン編集)には
useFetcherを使用する - URL を変更する場合(作成、削除後に一覧へ戻る)には
<Form>とredirect(...)を使用する - 保留中の状態は
useNavigation()またはfetcher.stateから導出し、useStateは使用しない - 行単位の保留中の状態には、ページ全体の
useNavigationではなく行ごとのfetcher.stateを使用する -
useNavigation()の呼び出しではnavigation.formActionを確認し、想定されるパスに対象を限定する - 楽観的 UI は
fetcher.formData/navigation.formDataを直接読み取り、ミラーリングしない - アクションが成功した場合は
redirect(...)を返し、json()を返すのはエラー時または同じページにとどまる場合のみ - ファイルアップロードを行うすべてのフォームで
<Form encType="multipart/form-data">を使用する -
unstable_createMemoryUploadHandlerには必ずmaxPartSizeを指定し、大きなファイルにはディスク/ストリームハンドラーを使用する -
FormDataの値は DB に渡す前に検証/型変換し、form.get(x) as stringのようなコードは使用しない - 1 つのルートに複数の変更がある場合は、別々のルートではなくインテントパターンを使用する
-
method="put|patch|delete"は JavaScript 専用であることを明記するか、POST とインテントの組み合わせに書き換える -
nav.formMethod/fetcher.formMethodは大文字の文字列("POST"、"GET")と比較する。v2 のv2_normalizeFormMethodはデフォルトで大文字を返すため、=== "post"は一致しません。
有効なパターン(指摘しない)
以下は Remix v2 の正しい使い方であり、報告すべきではありません。
actionプロパティのない<Form>— 慣例により現在の URL に POST します。actionの明示は任意です。- GET の
<Form>— 検索/フィルター UI に適しています。フォームのフィールドを URL の検索パラメーターとして loader に渡し、action は呼び出しません。ほとんどの「衛生」ルール(intent、redirect、encType)は POST フォームにのみ適用されます。 - 1 ページで複数の
useFetcher()インスタンスを使用する — 呼び出しごとに独立した送信チャネルが返されます。異なる行への変更を並行して行う場合に意図的に使用します。 - イベントハンドラー内の
useSubmit()— 自動保存、キーボードショートカット、onChangeによるトリガーに適したプログラムによる送信方法です。 - 送信中に
fetcher.formDataを読み取る — 意図された使い方であり、楽観的 UI の標準的なデータソースです。 - 送信後も
useActionDataのデータが保持される — 既知の動作です。次のナビゲーションまたはアクションが発生するまで、最後のアクション結果を返します。 <Form>のnavigate={false}— フェッチャーフォームに変換します。フェッチャーへの参照を保持しない<fetcher.Form>と同等です。parseMultipartFormData/ アップロードハンドラーのunstable_プレフィックス — v2 では固定されています。「不安定な API」として指摘しないでください。
コンテキストに応じたルール
以下の条件を満たす場合に限り、指摘してください。
| 問題 | 次の場合に限り指摘 |
|---|---|
<Form> ではなくネイティブの <form> を使用 | メソッドが POST で、ルートに action がある場合 — GET フォームと外部 URL のフォームは問題ありません |
| 保留中の状態がない | フォームが POST で、コンポーネント内のどこにも useNavigation() / fetcher.state の読み取りがない場合 |
作成後にアクションが json({ ok: true }) を返す | ルートが「/new」または作成画面の場合 — 同じページの編集フォームが JSON を返すのは問題ありません |
method="put" / "patch" / "delete" | その画面でプログレッシブエンハンスメントが対象となる場合(公開アプリ)— 管理用ツールや JS 専用ツールは、ドキュメントに記載があれば対象外にできます |
上限のない unstable_createMemoryUploadHandler | アップロード対象がユーザー指定のファイルである場合(サイズが固定された内部アーティファクトではない場合) |
| ミューテーションごとに別々のルートがある | ミューテーションが、互換性のある認証ルールを持つ同じリソースを操作する場合 — ルールの異なる関連リソースは問題ありません |
formAction フィルターなしの useNavigation() | 誤検出を引き起こすほかのナビゲーション対象(サイドバーの <Link>、関連フォーム)がコンポーネントに含まれる場合 |
fetcher.formData を state にミラーリング | その値がユーザーに表示される要素(ボタンのラベル、件数、トグル)を制御する場合 — ローカルな「編集中」フラグは該当しません |
ハードゲート(指摘を書く前に実施)
以下を順番に実行してください。報告しようとしている一連の問題について、すべてのゲートを通過するまで、ユーザー向けの指摘を下書きしないでください。
-
場所の証拠 — 合格: 各問題に、リポジトリのパスと、読んだファイルから引用した行範囲または短い逐語引用を記載します(記憶や差分だけに基づく推測は不可)。ルートモジュール、コンポーネント、および存在する場合は
actionを明記します。 -
適用除外の確認 — 合格: 各問題について、有効なパターン(指摘しない)および該当するコンテキストに応じたルールの項目に該当しない理由を、1 行で説明できます。
-
フォームメソッドの確認 — 合格: intent、
encType、redirect、または保留中の状態の欠如を指摘する前に、そのフォームがmethod="post"(またはput|patch|delete)であることを確認します。GET フォームは検索やフィルターに使用され、アクションではなくローダーを呼び出すため、POST フォームのルールを適用すると誤検出になります。 -
プロトコル — 合格: このレビューについて、review-verification-protocol の事前報告検証チェックリストを完了しています。
追加ドキュメント
- フォームと fetcher の誤用 — 手動の
fetch()、ネイティブの<form>、誤ったプリミティブ: references/form-vs-fetcher.md - 保留状態のアンチパターン —
useStateフラグ、ページ全体と行単位の違い、状態の欠如: references/pending-state.md - アップロードと FormData の検証 — 上限のないハンドラー、
encTypeの欠如、未検証のキー、ミラーリングされた楽観的状態: references/uploads-validation.md - 複数アクションのルート — intent パターンの違反、PE フォールバックのない PUT/DELETE、送信ボタンの欠如: references/multi-action-routes.md
- 標準的なパターン — 関連する remix-v2-forms を参照してください。
リファレンスを読み込むタイミング
fetch()/axios/ ネイティブの<form>を呼び出すフォーム、または<Form>とuseFetcherのどちらを使うかを確認する場合 → form-vs-fetcher.md- 読み込みフラグ、スピナー、ボタンの無効化ロジック、行単位の保留状態を確認する場合 → pending-state.md
- ファイルのアップロード、
unstable_*ハンドラー、FormData の解析、楽観的 UI を確認する場合 → uploads-validation.md - 複数のミューテーション、intent フィールド、PUT/DELETE メソッドを含むルートを確認する場合 → multi-action-routes.md
レビューの確認事項
- アプリ内のすべての変更処理は、ルートの
actionを経由していますか(手動のfetch()はありませんか)? <Form>とuseFetcherのどちらを使うかは、URLを変更するかどうかに基づいていますか?- 保留中の状態は
useNavigation()/fetcher.stateから取得していますか(useStateは使っていませんか)? - 行ごとのスピナーは、ページ全体の
useNavigationではなく、行ごとのfetcher.stateに接続されていますか? - ファイルアップロードフォームでは
encType="multipart/form-data"を設定し、上限のあるハンドラーを使用していますか? - FormData の値は、DB に渡す前に検証されていますか?
- 1つのリソースに対する複数の変更処理には、別々のルートではなくインテントパターンを使用していますか?
- POST フォームには、プログレッシブエンハンスメントに対応する実際の送信ボタンが少なくとも1つありますか?
誤検出に関する注意事項
- ルート以外のコンポーネント内に
<Form>がレンダリングされていても、最も近いルートのactionに紐づいています。「action がない」と指摘する前に、ルートファイルを確認してください。 - 送信が成功した後に
useActionData()がデータを返すのは想定された動作です。データは次のナビゲーションまで保持されます。成功バナーが閉じる手段なしに無条件で表示される場合に限り、指摘してください。 Formを別名でインポートしているコード(例:import { Form as RemixForm })も Remix コンポーネントです。ローカル名ではなく、インポート元に基づいて判定してください。
指摘事項を提出する前に
ハードゲート(特にゲート4)を完了してから、review-verification-protocol の報告前チェックを引き続き通過する問題のみを報告してください。