データ授受に関する設定
前回に続いて、ルートモジュールに設定できるデータ授受に関する設定について解説します。この区分に分類できるのは、表1の5つです。
名称 | 概要 |
---|---|
loader | データフェッチのためのサーバー側の関数 |
action | フォームからのsubmitリクエストを受け付けるサーバー側の関数 |
clientLoader | データフェッチのためのクライアント側の関数 |
clientAction | フォームからのsubmitリクエストを受け付けるクライアント側の関数 |
shouldRevalidate | どのようなイベントが発生したときにloaderを再実行するのかを定義する |
数は多いですが、基本的には、サーバー側で実行する loader
と action
と、ブラウザ側で実行する clientLoader
と clientAction
という対になる関数があり、それぞれの使い方も似ているので、さほど覚えることは多くないはずです。それでは、見ていきましょう。
loader / clientLoader
まず解説するのは、Remixの最大の特徴の1つともいえる、loader
です。これは、サーバー側で実行される関数で、データフェッチのために使います。もう1つ、ブラウザ側のデータを loader
と同じスキームで取得するために用意された、clientLoader
についても合わせて解説しましょう。リスト1のように定義します。
import { json } from '@remix-run/node'; export async function loader({ params }) { const { itemId } = params; const item = await fetch(`https://example.com/item/${itemId}`) .then((res) => res.json()); // (1) return json({ item, }); } export async function clientLoader({ serverLoader }) { const serverData = await serverLoader(); // (3) const settings = getSettings(); // (4) // (5) return { ...serverData, settings, }; } export default function Component() { const { item, settings } = useLoaderData(); // (6) // 省略 }
loader
については本連載の中で何度か例示してきたように、サーバー側でデータフェッチを行い、その結果のみをブラウザへと返すものです。戻り値としては、原則としてWeb標準の Response
型のデータを返すことになっていますが、JSONとして表現可能なオブジェクトを返す場合には、(1)のように json()
関数を使って、Content-Type: application/json
を設定済みの Response
型のデータを返すこともできます。
本連載の中では少し見慣れないのが、(2)の clientLoader
です。これはその名の通り、ブラウザ側で実行されるloaderです。特徴としては、(3)のように、引数で受け取った serverLoader
関数で loader
関数の結果を受け取りつつ、(4)のタイミングでブラウザ独自の処理を行った上で、(5)のようにコンポーネントに対してデータを渡すことができます。 ブラウザのAPIからデータを取り出す際には、clientLoader
を利用するのがオススメです。また、処理時間が長そうな場合には、前回解説した HydrateFallback
を併用するとよいでしょう。
最終的には、(6)のように、useLoaderData
フックを使って、clientLoader
で返したデータを取り出します。loader
と clientLoader
のどちらを使っているのかは、コンポーネント側はあまり意識する必要はありません。
さて、loader
や clientLoader
が引数でオブジェクトとして受け取れるデータについても確認しておきましょう。受け取れるデータは、次の通りです。
-
params
:URLパスのパラメーター -
request
:HTTPリクエスト -
context
:(loader
のみ)Remixのコンテキスト -
serverLoader
:(clientLoader
のみ)loader
が返したデータを取得するための関数
params
は、mypage.$itemId.jsx
のようにファイル名にパラメータを持つルートモジュールの場合に、loader
や clientLoader
の中で params.itemId
としてURL内のパラメータを呼び出すことができるプロパティです。request
は、Web標準のRequest型のデータで、HTTPリクエストの情報を取得するために使います。request.url
でページのURL全体が取れるので、Search Params(いわゆるクエリパラメータ)を読み出すことも可能です。context
は、Remixのコンテキストを取得するために使います。Node.js環境で動かしていると使う機会が少ないのですが、Cloudflare Workers等の環境では、AIやデータベースといった組み込みサービスを呼び出すために使用されることもあります(リスト2)。
export const loader = ({ context }) => { // contextからCloudflare WorkersのAPIにアクセスできる const { env } = context.cloudflare; const answer = await env.AI.run('@cf/meta/llama-2-7b-chat-int8', { prompt: "What is the origin of the phrase 'Hello, World'" }); // 略 };
serverLoader
は、clientLoader
でのみ利用可能なプロパティで、loader
が返したデータを取得するために使います。
action / clientAction
次は、action
と clientAction
です。これは、フォームからのsubmitリクエストを受け付けるための関数です。action
はサーバー側で実行される関数で、clientAction
はブラウザ側で実行される関数です。リスト3のように定義します。
import { json } from '@remix-run/node'; import { useLoaderData, useActionData, Form } from '@remix-run/react'; export async function loader({ params }) { return json({ itemId: params.itemId }); } export async function action({ params, request }) { // (1) const { itemId } = params; // (3) const formData = await request.formData(); const title = await formData.get('title'); // (5) バリデーションを行う if (!title) { return json({ success: false, message: 'Title is required', }); } await fetch(`/api/items/${itemId}`, { method: 'POST', body: JSON.stringify({ title }), }); return json({ success: true }); } export async function clientAction({ serverAction }) { /* サーバーへデータを送信する前に、ブラウザ側でやっておきたいことがあればここで実施する */ // (4) const result = await serverAction(); return { ...result, // ここで何かしらの値を追加することもできる }; } export default function ActionComponent() { const { itemId } = useLoaderData(); const actionData = useActionData(); console.log('itemId', itemId); console.log('actionData', actionData); return ( <div> <h1>Action</h1> {actionData?.success === false && ( // (6) <p style={{ color: 'red' }}> {actionData.message} </p> )} <Form method="post"> <input type="text" name="title" />{/* (2) */} <button type="submit">Submit</button> </Form> </div> ); }
action
は loader
と同じインターフェースを持つため、(1)のように params
からルーティングに定義したパラメータ($itemId
)を受け取ることができます。また、(2)のようなフォームからのリクエストを受け付ける action
ならではの特性として、(3)のように request
からFormData型のデータを取り出すことができます。
clientAction
はブラウザ側で実行される関数で、ほとんど action
と同じインターフェースを持ちます。(4)の serverAction()
関数をパラメータから受け取れる点が action
との主な違いです。serverAction()
関数を実行すると、サーバー側の action
が実行されます。この点も clientLoader
と類似していますね。なお、clientAction
はあまり使い所が多くありません。ブラウザ内に独自にキャッシュを構築している場合に、そのキャッシュを更新するために使う方法が公式ドキュメントで示唆されていますが、それ以外の場合には、action
で十分です。
useActionData()
フックを使うことで、action
や clientAction
で返したデータを取得することができます。使いどころとしては、(5)のようにサーバー側でバリデーションした結果をブラウザに返すために使うことが多いでしょう。バリデーションの結果を使って、(6)のように、エラーメッセージを表示するような使い方が有効です。(5)のチェックによって title
が空の場合にエラーメッセージを発行するので、リスト3のコードを実行して、フォームに何も入力せずに送信すると、図1のようにエラーメッセージが表示されます。
action
でバリデーションを行うことで、検証内容の詳細な処理をブラウザに対して隠せるメリットがあります。フォームの送信時には、action
と useActionData
を活用することをおすすめします。
shouldRevalidate
データ授受の最後に、shouldRevalidate
について解説します。これは、画面遷移やデータ送信のイベントごとにブラウザ側で実行して、loader
を再実行するかどうかを判定するための関数です。リスト4のように定義します。
export const shouldRevalidate = ({ actionResult, currentParams, currentUrl, defaultShouldRevalidate, formAction, formData, formEncType, formMethod, nextParams, nextUrl, }) => { // (1) loaderを再実行するならtrueを返す if (nextParams.forceReload === true) { // (3) return true; } // (2) loaderを再実行しないならfalseを返す if (/* loaderを再実行しない条件 */) { return false; } return defaultShouldRevalidate; // 特に条件を指定しない場合はこちらを使う };
(1)のように、最終的に true
を返せば再実行する、(2)のように false
を返せば再実行しない、という単純な仕組みです。各種イベントの内容を検知するために、引数からは次のような値が受け取れます。
-
フォームからのデータ送信
-
formAction:リクエスト時の
action
属性の値 -
formData:リクエスト時の
FormData
型のデータ - formEncType:リクエスト時のデータのエンコーディング
-
formMethod:リクエスト時の
method
属性の値 -
actionResult:
action
やclientAction
関数の戻り値
-
formAction:リクエスト時の
-
画面遷移
- currentParams:現在のURLパスのパラメーター
- currentUrl:現在のURL
- nextParams:次のURLパスのパラメーター
- nextUrl:次のURL
-
その他
- defaultShouldRevalidate:デフォルトの判定結果
基本的には、関心のある値の変化のみを判定して、それ以外のケースでは defaultShouldRevalidate
を返すのがベストプラクティス
のようです。defaultShouldRevalidate
には、loader
が実行されうるタイミングごとに最適な値が渡されます。
たとえば、shouldRevalidate()
をデフォルト設定のままにして、/users/123/article/abc
から /users/123/article/def
へ画面遷移した場合について考えてみましょう。パスだけで見れば app/routes/users.$user_id.jsx
と app/routes/users.$user_id.article.$article_id.jsx
の loader
が実行されそうです。しかし、実際には、後者の loader
だけが実行されます。これは、画面遷移の前後で /users/123
というパスが変わっていないため、app/routes/users.$user_id.jsx
の loader
を再実行する必要がないとRemixが判断して、defaultShouldRevalidate
に false
を渡したからです。このように、特段の理由がない限りは defaultShouldRevalidate
の挙動に任せられるように shouldRevalidate()
を実装しておくとよいでしょう。
特定の操作による画面遷移のときだけ loader
を再実行したい場合には、app/routes/users.$user_id.jsx
の shouldRevalidate
へ、(3)のような設定を。行うとよいでしょう。forceReload
というパラメータをURLに付与して、そのパラメータがある場合には true
を返すようにしておくと、特定の操作による画面遷移のときだけ loader
を再実行することができます。つまり、/users/123/article/abc
から /users/123/article/def?forceReload=true
への画面遷移した際に、app/routes/users.$user_id.jsx
の loader
を再実行することができます。
まとめ
今回は、ルートモジュールに設定できる各種の設定のうち、データ授受に関する設定について解説しました。loader
や action
といった基本的な設定から、clientLoader
や clientAction
といったブラウザ側で実行される設定、shouldRevalidate
といった loader
の再実行を制御する設定まで、幅広い設定項目を見てきました。Webアプリケーションにとって通信は欠かせない要素ですが、Remixを使えば、それらの設定を簡単に行うことができます。
これらのAPIを活用した、Remixと相性のよいフォーム管理やバリデーションの手法もあるので、本連載の後の回に解説できればと思います。
次回はルーティング設定の最後のトピックとして、HTTPヘッダーやmeta要素の設定など、ルートモジュールに設定できるその他の設定について解説します。お楽しみに!