SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Remixを通じてWebを学ぶ

豊富なAPIが揃うRemix──ルートモジュールの設定項目を徹底解説!

Remixを通じてWebを学ぶ 第7回

  • X ポスト
  • このエントリーをはてなブックマークに追加

データ授受に関する設定

 前回に続いて、ルートモジュールに設定できるデータ授受に関する設定について解説します。この区分に分類できるのは、表1の5つです。

表1:データ授受に関する設定用の関数
名称 概要
loader データフェッチのためのサーバー側の関数
action フォームからのsubmitリクエストを受け付けるサーバー側の関数
clientLoader データフェッチのためのクライアント側の関数
clientAction フォームからのsubmitリクエストを受け付けるクライアント側の関数
shouldRevalidate どのようなイベントが発生したときにloaderを再実行するのかを定義する

 数は多いですが、基本的には、サーバー側で実行する loaderaction と、ブラウザ側で実行する clientLoaderclientAction という対になる関数があり、それぞれの使い方も似ているので、さほど覚えることは多くないはずです。それでは、見ていきましょう。

loader / clientLoader

 まず解説するのは、Remixの最大の特徴の1つともいえる、loader です。これは、サーバー側で実行される関数で、データフェッチのために使います。もう1つ、ブラウザ側のデータを loader と同じスキームで取得するために用意された、clientLoader についても合わせて解説しましょう。リスト1のように定義します。

[リスト1]app/routes/loader.$itemId.jsx
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 で返したデータを取り出します。loaderclientLoader のどちらを使っているのかは、コンポーネント側はあまり意識する必要はありません。

 さて、loaderclientLoader が引数でオブジェクトとして受け取れるデータについても確認しておきましょう。受け取れるデータは、次の通りです。

  • params:URLパスのパラメーター
  • request:HTTPリクエスト
  • context:(loaderのみ)Remixのコンテキスト
  • serverLoader:(clientLoaderのみ) loader が返したデータを取得するための関数

 params は、mypage.$itemId.jsx のようにファイル名にパラメータを持つルートモジュールの場合に、loaderclientLoader の中で params.itemId としてURL内のパラメータを呼び出すことができるプロパティです。request は、Web標準のRequest型のデータで、HTTPリクエストの情報を取得するために使います。request.url でページのURL全体が取れるので、Search Params(いわゆるクエリパラメータ)を読み出すことも可能です。context は、Remixのコンテキストを取得するために使います。Node.js環境で動かしていると使う機会が少ないのですが、Cloudflare Workers等の環境では、AIやデータベースといった組み込みサービスを呼び出すために使用されることもあります(リスト2)。

[リスト2]Cloudflare WorkersでAIを扱う例
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

 次は、actionclientAction です。これは、フォームからのsubmitリクエストを受け付けるための関数です。action はサーバー側で実行される関数で、clientAction はブラウザ側で実行される関数です。リスト3のように定義します。

[リスト3]app/routes/action.$itemId.jsx
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>
  );
}

 actionloader と同じインターフェースを持つため、(1)のように params からルーティングに定義したパラメータ($itemId)を受け取ることができます。また、(2)のようなフォームからのリクエストを受け付ける action ならではの特性として、(3)のように request からFormData型のデータを取り出すことができます。

 clientAction はブラウザ側で実行される関数で、ほとんど action と同じインターフェースを持ちます。(4)の serverAction() 関数をパラメータから受け取れる点が action との主な違いです。serverAction() 関数を実行すると、サーバー側の action が実行されます。この点も clientLoader と類似していますね。なお、clientAction はあまり使い所が多くありません。ブラウザ内に独自にキャッシュを構築している場合に、そのキャッシュを更新するために使う方法が公式ドキュメントで示唆されていますが、それ以外の場合には、action で十分です。

 useActionData() フックを使うことで、actionclientAction で返したデータを取得することができます。使いどころとしては、(5)のようにサーバー側でバリデーションした結果をブラウザに返すために使うことが多いでしょう。バリデーションの結果を使って、(6)のように、エラーメッセージを表示するような使い方が有効です。(5)のチェックによって title が空の場合にエラーメッセージを発行するので、リスト3のコードを実行して、フォームに何も入力せずに送信すると、図1のようにエラーメッセージが表示されます。

図1:フォームのバリデーションエラー
図1:フォームのバリデーションエラー

 action でバリデーションを行うことで、検証内容の詳細な処理をブラウザに対して隠せるメリットがあります。フォームの送信時には、actionuseActionData を活用することをおすすめします。

shouldRevalidate

 データ授受の最後に、shouldRevalidate について解説します。これは、画面遷移やデータ送信のイベントごとにブラウザ側で実行して、loader を再実行するかどうかを判定するための関数です。リスト4のように定義します。

[リスト4]app/routes/should-revalidate.jsx
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: actionclientAction 関数の戻り値
  • 画面遷移
    • 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.jsxapp/routes/users.$user_id.article.$article_id.jsxloader が実行されそうです。しかし、実際には、後者の loader だけが実行されます。これは、画面遷移の前後で /users/123 というパスが変わっていないため、app/routes/users.$user_id.jsxloader を再実行する必要がないとRemixが判断して、defaultShouldRevalidatefalse を渡したからです。このように、特段の理由がない限りは defaultShouldRevalidate の挙動に任せられるように shouldRevalidate() を実装しておくとよいでしょう。

 特定の操作による画面遷移のときだけ loader を再実行したい場合には、app/routes/users.$user_id.jsxshouldRevalidate へ、(3)のような設定を。行うとよいでしょう。forceReload というパラメータをURLに付与して、そのパラメータがある場合には true を返すようにしておくと、特定の操作による画面遷移のときだけ loader を再実行することができます。つまり、/users/123/article/abc から /users/123/article/def?forceReload=true への画面遷移した際に、app/routes/users.$user_id.jsxloader を再実行することができます。

まとめ

 今回は、ルートモジュールに設定できる各種の設定のうち、データ授受に関する設定について解説しました。loaderaction といった基本的な設定から、clientLoaderclientAction といったブラウザ側で実行される設定、shouldRevalidate といった loader の再実行を制御する設定まで、幅広い設定項目を見てきました。Webアプリケーションにとって通信は欠かせない要素ですが、Remixを使えば、それらの設定を簡単に行うことができます。

 これらのAPIを活用した、Remixと相性のよいフォーム管理やバリデーションの手法もあるので、本連載の後の回に解説できればと思います。

 次回はルーティング設定の最後のトピックとして、HTTPヘッダーやmeta要素の設定など、ルートモジュールに設定できるその他の設定について解説します。お楽しみに!

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Remixを通じてWebを学ぶ連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/19418 2024/05/20 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング