mcpField
R
remix-v2-forms

remix-v2-forms

github.com/existential-birds開発8541 回インストール11 週間前に更新
リポジトリ

remix-v2-forms について

Remix v2 開発者向けのフォームとミューテーションのスキルです。action、フォーム、状態フックの連携を整理し、プログレッシブエンハンスメントとデータ再検証を支援します。

主な機能

  1. 01画面遷移と画面内送信の使い分け
  2. 02action でのフォームデータ処理
  3. 03エラーと処理中状態の参照
  4. 04楽観的 UI とファイルアップロード
  5. 05用途に応じた Remix API の選択

活用シーン

  1. 01作成後にレコード詳細へ遷移
  2. 02リスト項目の編集や状態切り替え
  3. 03全体または局所の処理中表示
  4. 04検証付きファイルアップロードの実装

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/existential-birds/beagle --skill remix-v2-forms

インストール後、Claude Code で/remix-v2-formsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
fetcher.md
form.md
intent-actions.md
optimistic-ui.md
uploads.md

詳細

Remix v2 のフォームとミューテーション

@remix-run/react@^2 のルートモジュールフレームワークで標準となるミューテーションの基本機能です。正しい Remix v2 のミューテーションは、<Form method="post">(または <fetcher.Form>)、request.formData() を解析して redirect(...) または json(...) を返す action、そしてエラーには useActionData()(または fetcher.data)、保留状態には useNavigation()(または fetcher.state)を参照する UI で構成されます。この一連の処理を迂回する方法(fetch()、通常の <form>、e.preventDefault() とクライアント状態の組み合わせ)では、再検証、プログレッシブエンハンスメント、競合に安全な遷移がひそかに失われます。

クイックリファレンス

<Form> と action:

import { json, redirect, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useActionData, useNavigation } from "@remix-run/react";

export async function action({ request }: ActionFunctionArgs) {
  const form = await request.formData();
  const email = String(form.get("email") ?? "");
  if (!email.includes("@")) return json({ errors: { email: "Invalid" } }, { status: 400 });
  await createUser({ email });
  return redirect("/dashboard");
}

export default function Signup() {
  const actionData = useActionData<typeof action>();
  const nav = useNavigation();
  const busy = nav.state !== "idle" && nav.formAction === "/signup";
  return (
    <Form method="post" replace>
      <input name="email" type="email" />
      {actionData?.errors?.email ? <em>{actionData.errors.email}</em> : null}
      <button disabled={busy}>{busy ? "Signing up..." : "Sign Up"}</button>
    </Form>
  );
}

基本機能

名前目的
@remix-run/react の <Form>ルートの action にデータを送信し、ページ全体の再検証を実行する、ナビゲーション可能でプログレッシブエンハンスメントに対応したフォーム
<Form navigate={false}>「fetcher 経由で送信し、ナビゲーションしない」ための簡略表記です。fetcher の参照を保持しない <fetcher.Form> と同等で、プログラムから操作するためのハンドルではなく、保留状態だけが必要な場合に便利です
useFetcher()インラインのミューテーション、リスト行、ポップオーバーなどで使う、ナビゲーションを伴わない送信チャネルです。同じ再検証を行い、URL は変更されません
useFetchers()アプリ全体で進行中のすべての fetcher 状態を含む読み取り専用の配列です。props のバケツリレーなしで、グローバルな保留インジケーター(トップバーのローダーなど)に使用します。返される項目には Form/submit/load メソッドはなく、formData、state などだけが含まれます
useNavigation()ページレベルのナビゲーションを監視します。<Form> の保留状態を示す信頼できる情報源です
useSubmit()プログラムによる送信(onChange による自動保存、キーボードショートカットなど)に使用します。HTMLFormElement、FormData、通常のオブジェクト(フォームエンコード)、または { encType: "application/json" } を指定して JSON としてエンコードした通常のオブジェクトを受け取ります
useActionData<typeof action>()現在のルートで直近に実行された action の結果を読み取ります

状態遷移:

  • useNavigation().state:GET 以外のフォーム送信では idle → submitting → loading → idle、GET ナビゲーションでは idle → loading → idle です。
  • useFetcher().state:idle → submitting → loading → idle です。

非対称性: GET ナビゲーションでは useNavigation は submitting を経由しません。useFetcher は経由します。例外は fetcher.load() のみで、こちらは経由しません。<fetcher.Form method='get'> と fetcher.submit(..., {method:'get'}) はどちらも submitting を経由します。

主なパターン

ナビゲーションには <Form>、その場での処理には useFetcher

<Form> は URL を変更して履歴に追加し、すべての loader を再検証します。 useFetcher も同じ再検証を行いますが、現在の URL にとどまります。 各 useFetcher() 呼び出しは独立した送信チャネルを返すため、 2 つの行が同時に送信されても、保留状態は共有されません。

1 つのルートで複数のアクションを扱う intent パターン

1 つの action で formData.get("intent") を切り替え、操作ごとに <button name="intent" value="..."> を使います。クリックした送信ボタンの name=value だけがリクエスト本文に含まれます。references/intent-actions.md を参照してください。

formData を使った楽観的 UI

fetcher.formData と navigation.formData は送信時に同期的に設定され、 idle になるとクリアされます。レンダーごとに直接読み取り、ローカルの React 状態に複製しないでください。references/optimistic-ui.md を参照してください。

ファイルアップロードには encType="multipart/form-data" が必要です

これを指定しないと、request.formData() はファイルデータを取り除き、 File ではなくファイル名の文字列を返します。 unstable_parseMultipartFormData と、サイズ上限を設定したアップロードハンドラーで 解析してください。unstable_ のプレフィックスは v2 でも恒久的に付きます。 references/uploads.md を参照してください。

ゲート(判断の順序)

順番どおりに回答してください。Pass は条件が真であることを意味します。同じ行にある API を選び、そこで終了してください。

<Form> vs useFetcher
  1. ミューテーション後に URL を変更する必要がありますか(レコードを作成して /records/:id に移動する、削除してリストに戻る、 複数ステップのフローなど)?
    • はい → <Form method="post"> と、action からの redirect(...) を使います。終了。
    • いいえ → ステップ 2。
  2. ユーザーが同じページにとどまったまま、行、セル、トグル、または サブセクションに対してミューテーションを行いますか(お気に入り、いいね、 数量の増加、インライン編集など)?
    • はい → <fetcher.Form> を使う useFetcher() を使います。終了。
    • いいえ → ステップ 3。
  3. 通常のナビゲーション以外でデータを読み込みますか(ポップオーバーの内容、 コンボボックスの結果、プリフェッチなど)?
    • はい → fetcher.load(href) を使います。終了。
    • いいえ → <Form> をデフォルトとして使います。ナビゲーションが最も堅実な 選択肢です。再検証と履歴が標準で機能します。

厳守事項:アプリ内で独自の Remix ルートに対してミューテーションを行う際に、 fetch() や axios を使ってはいけません。これらを使うと action のライフサイクルが迂回され、 loader の再検証が行われません。

保留中の状態を示す useNavigation と useFetcher.state
  1. 保留中を示すインジケーターはグローバルですか(root のページスピナー、上部の 読み込みバーなど)?
    • はい → root.tsx で useNavigation() を使います (navigation.state !== "idle")。終了。
    • いいえ → ステップ 2。
  2. ミューテーションは useFetcher で実行しましたか?
    • はい → その fetcher の fetcher.state を使います。useNavigation() は fetcher の処理状況を反映しません。終了。
    • いいえ → ステップ 3。
  3. リスト内の 1 つの行やボタンに限定されたインジケーターで、各行に 独自の fetcher がありますか?
    • はい → 行ごとの fetcher.state を使います(または useFetchers() でキーを指定して 取得します)。これにより、ほかの行がちらつくのを防げます。終了。
    • いいえ → ステップ 4。
  4. インジケーターは <Form> で送信したフォームに限定されますか?
    • はい → useNavigation() を使い、 navigation.formAction === "/expected-path" も確認して、無関係なナビゲーションで ローカルのスピナーが起動しないようにします。終了。
    • いいえ → ステップ 5。
  5. 楽観的な値をレンダリングする必要がありますか?
    • はい → navigation.formData?.get("field")(ページのフォーム)または fetcher.formData?.get("field")(fetcher)を読み取ります。どちらも state !== "idle" の間は値が設定されています。終了。

追加ドキュメント

  • <Form> コンポーネント:<Form> とネイティブの <form> と fetch() の違い、 プログレッシブエンハンスメント、成功後のリダイレクト、useActionData による バリデーションエラーの表示については、references/form.md を参照してください。
  • useFetcher:インラインミューテーション、リスト操作、ポップオーバー、fetcher.state、 fetcher.data、fetcher.Form、fetcher.submit、fetcher.load については、 references/fetcher.md を参照してください。
  • 楽観的 UI:fetcher.formData と useNavigation.formData、適用するタイミング、 失敗時の取り消しについては、 references/optimistic-ui.md を参照してください。
  • ファイルのアップロード:unstable_parseMultipartFormData、 unstable_createMemoryUploadHandler、 unstable_createFileUploadHandler、および上限付きハンドラーについては、 references/uploads.md を参照してください。
  • インテントベースのアクション:FormData の intent フィールドを使って 1 つのルートで 複数のアクションを実行する方法については、 references/intent-actions.md を参照してください。

比較

懸念事項<Form>useFetcherネイティブの <form>fetch()
URL の変更/履歴エントリーはいいいえはい(ハードナビゲーション)いいえ
JS なしで動作はいはいはいいいえ
loader を再検証はいはいはい(ハードリロード)いいえ
保留中の状態を取得するフックuseNavigation()fetcher.stateなし手動
楽観的入力の取得元navigation.formDatafetcher.formDataなし手動
アプリ内ミューテーションの用途作成/削除/複数ステップインライン/行/トグル外部ターゲットのみ独自のルートには使用しない

コメント