データ書き込み
Remix におけるデータ書き込み(ミューテーションと呼ぶ人もいます)は、2 つの基本的な Web API である <form>
と HTTP の上に構築されています。そして、プログレッシブエンハンスメントを使用して、楽観的な UI、ローディングインジケーター、およびバリデーションフィードバックを有効にしますが、プログラミングモデルは依然として HTML フォームに基づいています。
ユーザーがフォームを送信すると、Remix は次の処理を行います。
- フォームのアクションを呼び出す
- ページ上のすべてのルートのすべてのデータをリロードする
多くの場合、人々は、サーバーの状態をコンポーネントに取り込み、ユーザーが変更したときに UI を同期させるために、redux のような React のグローバル状態管理ライブラリ、apollo のようなデータライブラリ、React Query のような fetch ラッパーを利用します。Remix の HTML ベースの API は、これらのツールのほとんどのユースケースを置き換えます。Remix は、標準の HTML API を使用すると、データのロード方法と、変更後に再検証する方法を認識します。
アクションを呼び出してルートを再検証する方法はいくつかあります。
このガイドでは、<Form>
のみを取り上げます。これら 2 つの使用方法を理解するために、このガイドの後にドキュメントを読むことをお勧めします。このガイドのほとんどは useSubmit
に適用されますが、useFetcher
は少し異なります。
プレーンな HTML フォーム
当社 React Training で長年ワークショップを開催してきた結果、多くの新しい Web 開発者(彼らのせいではありませんが)が、実際には <form>
の仕組みを知らないことがわかりました。
Remix の <Form>
は <form>
と同じように機能するため(楽観的な UI などのためのいくつかの追加機能があります)、プレーンな HTML フォームについて復習し、HTML と Remix の両方を同時に学習できるようにします。
HTML フォームの HTTP 動詞
ネイティブフォームは、GET
と POST
の 2 つの HTTP 動詞をサポートしています。Remix は、これらの動詞を使用して、あなたの意図を理解します。GET の場合、Remix はページのどの部分が変更されているかを判断し、変更されているレイアウトのデータのみをフェッチし、変更されていないレイアウトにはキャッシュされたデータを使用します。POST の場合、Remix はすべてのデータをリロードして、サーバーからの更新を確実にキャプチャします。両方を見てみましょう。
HTML フォーム GET
GET
は、フォームデータが URL 検索パラメーターで渡される通常のナビゲーションです。通常のナビゲーションに使用します。<a>
と同じですが、ユーザーはフォームを介して検索パラメーターでデータを提供できます。検索ページを除いて、<form>
での使用は非常にまれです。
次のフォームを考えてみましょう。
ユーザーがフォームに入力して送信をクリックすると、ブラウザーはフォームの値を自動的に URL 検索パラメーター文字列にシリアル化し、クエリ文字列を追加してフォームの action
に移動します。ユーザーが「remix」と入力したとしましょう。ブラウザーは /search?term=remix
に移動します。入力を <input name="q"/>
に変更すると、フォームは /search?q=remix
に移動します。
これは、次のリンクを作成した場合と同じ動作です。
ただし、ユーザーが情報を提供したという独自の違いがあります。
フィールドが多い場合、ブラウザーはそれらを追加します。
ユーザーがクリックしたチェックボックスに応じて、ブラウザーは次のような URL に移動します。
/search?brand=nike&color=black
/search?brand=nike&brand=reebok&color=white
HTML フォーム POST
Web サイトでデータを作成、削除、または更新する場合は、フォームの POST を使用します。そして、ユーザープロファイルの編集ページのような大きなフォームだけを意味するわけではありません。「いいね」ボタンでさえ、フォームで処理できます。
「新しいプロジェクト」フォームを考えてみましょう。
ユーザーがこのフォームを送信すると、ブラウザーはフィールドをリクエスト「ボディ」(URL 検索パラメーターではなく)にシリアル化し、サーバーに「POST」します。これは、ユーザーがリンクをクリックした場合と同じように、通常のナビゲーションです。違いは 2 つあります。ユーザーがサーバーにデータを提供し、ブラウザーがリクエストを「GET」ではなく「POST」として送信したことです。
データはサーバーのリクエストハンドラーで利用できるため、レコードを作成できます。その後、レスポンスを返します。この場合、おそらく新しく作成されたプロジェクトにリダイレクトします。Remix アクションは次のようになります。
ブラウザーは /projects/new
から開始し、リクエストにフォームデータを含めて /projects
に POST し、サーバーはブラウザーを /projects/123
にリダイレクトしました。これがすべて発生している間、ブラウザーは通常の「読み込み中」状態になります。アドレスのプログレスバーが埋まり、ファビコンがスピナーに変わるなどです。実際には、まともなユーザーエクスペリエンスです。
Web 開発を始めたばかりの場合は、この方法でフォームを使用したことがないかもしれません。多くの人が常に次のように行ってきました。
もしあなたがそうなら、ブラウザー(および Remix)に組み込まれているものを使用するだけで、ミューテーションがいかに簡単になるかを知って喜ぶでしょう。
Remix ミューテーション、最初から最後まで
次の手順で、ミューテーションを最初から最後まで構築します。
- JavaScript はオプション
- バリデーション
- エラー処理
- プログレッシブエンハンスメントされたローディングインジケーター
- プログレッシブエンハンスメントされたエラー表示
データミューテーションには、HTML フォームと同じように Remix の <Form>
コンポーネントを使用します。違いは、コンテキストローディングインジケーターや「楽観的な UI」のような、より優れたユーザーエクスペリエンスを構築するために、保留中のフォーム状態にアクセスできるようになったことです。
<form>
を使用するか <Form>
を使用するかに関係なく、まったく同じコードを記述します。<form>
から始めて、何も変更せずに <Form>
に移行できます。その後、特別なローディングインジケーターと楽観的な UI を追加します。ただし、その気がなかったり、締め切りが迫っている場合は、<form>
を使用して、ブラウザーにユーザーフィードバックを処理させましょう。Remix の <Form>
は、ミューテーションに対する「プログレッシブエンハンスメント」の実現です。
フォームの構築
以前のプロジェクトフォームから始めますが、使用可能にしましょう。
ルート app/routes/projects.new.tsx
に次のフォームがあるとします。
次に、ルートアクションを追加します。「post」であるフォーム送信は、データ「アクション」を呼び出します。「get」送信(<Form method="get">
)は、「ローダー」によって処理されます。
これで完了です!createProject
が期待どおりに機能すると仮定すると、これだけですべてです。過去に構築した可能性のある SPA の種類に関係なく、ユーザーからデータを取得するには、常にサーバー側のアクションとフォームが必要であることに注意してください。Remix の違いは、これだけが必要であるということです(そして、それが Web の昔の姿でもありました)。
もちろん、デフォルトのブラウザーの動作よりも優れたユーザーエクスペリエンスを作成しようとして、複雑なことを始めました。続行しましょう。そこに到達しますが、コア機能を実行するためにすでに記述したコードを変更する必要はありません。
フォームのバリデーション
フォームをクライアント側とサーバー側の両方で検証するのが一般的です。また、(残念ながら)クライアント側でのみ検証することも一般的であり、これにより、今すぐ説明する時間がないさまざまなデータの問題が発生します。重要なのは、1 か所でのみ検証する場合は、サーバーで検証することです。Remix を使用すると、それが唯一の場所であることがわかります(ブラウザーに送信するものが少ないほど良いです!)。
わかっています、わかっています、素敵なバリデーションエラーなどをアニメーション化したいのですよね。それについては後で説明します。しかし、今は基本的な HTML フォームとユーザーフローを構築しているだけです。最初はシンプルにして、後で凝ったものにします。
アクションに戻ると、次のようなバリデーションエラーを返す API があるかもしれません。
バリデーションエラーがある場合は、フォームに戻って表示する必要があります。
useLoaderData
が loader
から値を返すのと同じように、useActionData
はアクションからデータを返します。ナビゲーションがフォーム送信の場合にのみ存在するため、常に取得したかどうかを確認する必要があります。
すべての入力に defaultValue
を追加する方法に注目してください。これは通常の HTML <form>
であるため、通常のブラウザー/サーバーの処理が行われているだけです。サーバーから値が返されるため、ユーザーは入力した内容を再入力する必要はありません。
このコードをそのまま出荷できます。ブラウザーは、保留中の UI と中断を処理します。週末を楽しんで、月曜日に凝ったものにしましょう。
<Form>
に移行して保留中の UI を追加する
プログレッシブエンハンスメントを使用して、この UX を少し凝ったものにしましょう。<form>
から <Form>
に変更すると、Remix は fetch
でブラウザーの動作をエミュレートします。また、保留中のフォームデータにアクセスできるため、保留中の UI を構築できます。
ここで残りの作業を行う時間や意欲がない場合は、<Form reloadDocument>
を使用してください。これにより、ブラウザーは保留中の UI 状態(タブのファビコンのスピナー、アドレスバーのプログレスバーなど)を処理し続けることができます。保留中の UI を実装せずに <Form>
を使用するだけの場合、ユーザーはフォームを送信したときに何も起こっていないことに気づきません。
<Form reloadDocument>
プロパティを使用することをお勧めします。
次に、ユーザーが送信時に何かが起こったことを知るための保留中の UI を追加しましょう。useNavigation
というフックがあります。保留中のフォーム送信がある場合、Remix はフォームのシリアル化されたバージョンを FormData
オブジェクトとして提供します。最も関心があるのは、formData.get()
メソッドです。
かなり洗練されています!ユーザーが「作成」をクリックすると、入力が無効になり、送信ボタンのテキストが変更されます。ページ全体のリロード(ネットワークリクエストの増加、ブラウザーキャッシュからのアセットの読み取り、JavaScript の解析、CSS の解析などが発生する可能性があります)ではなく、1 つのネットワークリクエストのみが発生するため、操作全体も高速になるはずです。
このページでは navigation
をあまり使用しませんでしたが、送信に関するすべての情報(navigation.formMethod
、navigation.formAction
、navigation.formEncType
)と、サーバーで処理されているすべての値が navigation.formData
に含まれています。
バリデーションエラーのアニメーション化
このページを送信するために JavaScript を使用するようになったため、ページがステートフルであるため、バリデーションエラーをアニメーション化できます。まず、高さと不透明度をアニメーション化する凝ったコンポーネントを作成します。
これで、古いエラーメッセージをこの新しい凝ったコンポーネントでラップし、エラーのあるフィールドの境界線を赤色にすることもできます。
やった!サーバーとの通信方法を変更することなく、凝った UI ができました。また、JS の読み込みを妨げるネットワーク状態にも対応できます。
レビュー
-
まず、JavaScript を念頭に置かずにプロジェクトフォームを構築しました。サーバー側のアクションに投稿するシンプルなフォームです。1998 年へようこそ。
-
それが機能したら、
<form>
を<Form>
に変更して JavaScript を使用してフォームを送信しましたが、他に何もする必要はありませんでした。 -
React を使用したステートフルなページになったので、Remix にナビゲーションの状態を要求するだけで、ローディングインジケーターとバリデーションエラーのアニメーションを追加しました。
コンポーネントの観点から見ると、フォームが送信されたときに useNavigation
フックが状態の更新を引き起こし、データが返ってきたときに別の状態の更新が発生しただけです。もちろん、Remix の内部ではさらに多くのことが起こりましたが、コンポーネントに関する限り、それだけです。いくつかの状態の更新だけです。これにより、ユーザーフローを非常に簡単に装飾できます。