SHOEISHA iD

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

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

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

Remixを通じてWebを学ぶ

Remixのルーティングに関するAPIを徹底解説!~ルートモジュールの設定項目について~

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

コンポーネントに関する設定

 まずは、ルートモジュールに設定できるReactコンポーネントについて解説します。この区分に分類できるのは、表1の3つです。

表1:コンポーネントに関する設定
名称 概要
デフォルト このルートモジュールが通常の方法で表示するコンポーネント
ErrorBoundary ルートモジュール内で起きたエラーをハンドリングしてエラー表示を出すためのコンポーネント
HydrateFallback 初期表示時のclientLoader(※)が完了するまでの間に表示するコンポーネント

 Reactコンポーネントなので、設定しておくと、フレームワーク側から何らかのタイミングで呼び出され、UIとして表示されることになります。

(※)ブラウザ側でデータフェッチやWeb APIの実行を行い、コンポーネントにデータを渡すための関数

デフォルト

 デフォルトのエクスポート、つまり export default キーワードによって外部へ公開されるコンポーネントは、ブラウザで表示するURLがそのルートモジュールのファイルパスにマッチしたときに表示されるUIを表します。もう本連載の中では見慣れたものですね(リスト1)。

[リスト1]app/routes/default.jsx
export default function DefaultExportedComponent() {
  return (
    <main>
      <h1>Default Exported Component</h1>
      <span>このコンポーネントは<code>/default</code>のパスで表示されます</span>
    </main>
  );
}

 サンプルコードを手元で実行して、リスト1に該当する http://localhost:3000/default にアクセスすると、図1の画面が表示されます。

図1:デフォルトのコンポーネントが表示される
図1:デフォルトのコンポーネントが表示される

 注意すべき点として、このコンポーネントを表す関数は、サーバーで実行する(サーバーサイドレンダリング)のかブラウザで実行されるのかが、表示時の文脈による点があります。ブラウザのアドレスバーに直接URLを入力して表示する場合はサーバーで実行されますし、一度表示された後でサイト内を画面遷移するだけの場合はブラウザで実行される可能性が高いです。サーバーかブラウザのどちらかでしか実行できないAPIを扱ってエラーになるのは初学者の頻出ミスですので、注意してください。具体的には、ブラウザでしか実行できない window.navigator や、Node.jsサーバーでしか実行できない fs などをコンポーネントの中で使用すると、エラーが起こりやすくなります。

 なお、この問題は、loaderclientLoader を活用することで概ね解決できます。APIの詳細は次回で解説しますが、リスト2のように活用するのがオススメです。

[リスト2]ブラウザやサーバーに固有のAPIを利用する
import { useLoaderData } from '@remix-run/react';
import { json } from '@remix-run/node';

// サーバー側で実行されるデータ取得処理
export async function loader({ params }) {
  // URLのパスからIDを取得
  const { id } = params;
  // (1) サーバーのファイルシステムからデータを取得
  const items = await loadFile(id);

  // ブラウザへデータを返す
  return json({
    items,
  });
}

// クライアント側で実行されるデータ取得処理
export async function clientLoader({ params, serverLoader }) {
  // loaderの実行結果を取得
  const serverData = await serverLoader();
  // URLのパスからIDを取得
  const { id } = params;
  // (2) ブラウザのローカルストレージに保存していた設定値を取得
  const localSettings = window.localStorage.getItem(`Settings:${id}`);

  // コンポーネントへデータを渡す
  return {
    localSettings,
    ...serverData,
  };
}

export default function Component() {
  // (3) clientLoaderが返したデータを取得
  const { items, localSettings } = useLoaderData();

  return (/* 省略 */);
}

 (1)の loadFile() 関数は、サーバーのファイルシステムからデータを取得する処理を表しています。このようなサーバー側でしか実行できない処理は、loader で実行するようにします。一方、(2)はブラウザのローカルストレージからデータを取得する処理を表しています。このようなブラウザ側でしか実行できない処理は、clientLoader で実行するようにします。そして、(3)の useLoaderData() フックを使って、loaderclientLoader が返したデータを取得しています。このように、loaderclientLoader を使い分けることで、ブラウザやサーバーに固有のAPIを扱いつつ、コンポーネントからは同じようにデータを取得できるようになります。

ErrorBoundary

 次は ErrorBoundary です。Reactの公式ドキュメントでも、同名のデザインパターンが紹介されているので、名前を見たことがある方もいるかもしれません。

 Reactツリーのレンダリング中、つまりコンポーネントの関数やクラスを評価・実行している間に発生したエラーをキャッチして、ログ基盤にクラッシュレポートを送ったり、ユーザーにエラー画面を表示するためのデザインパターンが ErrorBoundary ですが、Remixでも同様に、ルートモジュール内で起きたエラーを上手くハンドリングするための機構として、同名のコンポーネントを定義することができます。Remixではリスト3のように定義します。

[リスト3]app/routes/error-boundary.jsx
import {
  isRouteErrorResponse,
  useRouteError,
} from "@remix-run/react";

// ErrorBoundaryコンポーネントを定義する
export function ErrorBoundary() {
  // (1) ルートモジュール内で発生したエラーを取得する
  const error = useRouteError();

  // (2) 通信エラーかどうかを判定する
  if (isRouteErrorResponse(error)) {
    // (3) 通信エラーに応じたUIを表示する
    return (
      <div>
        <h1>
          {error.status} {error.statusText}
        </h1>
        <p>{error.data}</p>
      </div>
    );
  } else if (error instanceof Error) {
    // (4) 通信エラーではないがエラーオブジェクトではある場合、エラーの内容を表示する
    return (
      <div>
        <h1>エラーが発生しました</h1>
        <p>{error.message}</p>
        <p>スタックトレース:</p>
        <pre>{error.stack}</pre>
      </div>
    );
  } else {
    // (5) 通信エラーでもエラーオブジェクトでもない場合、不明なエラーとして扱う
    return <h1>Unknown Error</h1>;
  }
}

 Remixの ErrorBoundary は、ルートモジュール内で起きたエラー全般に関心を持ちます。そのため、Reactコンポーネントのレンダリングだけではなく、loaderaction といった通信処理の実行中に発生したエラーもキャッチします。リスト3では、(1)の useRouteError フックを使って、ルートモジュール内で発生したエラーを取得しています。(2)では、取得したエラーが通信エラーかどうかを判定して、通信エラーであれば、(3)のようにエラーの内容を表示するUIを返します。通信エラーでない場合は、(4)のようにエラーの内容を表示するUIを返します。通信エラーでもエラーオブジェクトでもない場合は、(5)のように不明なエラーとして扱います。

 例えば、デフォルトのコンポーネント内に throw new Error("I am error") というコードを埋め込んで、エラーオブジェクトによるエラーを発生させると、図2のようにエラー画面が表示されます。

図2:エラー画面が表示される
図2:エラー画面が表示される

 この ErrorBoundary は、通常のUI(=デフォルトのコンポーネント)の表示がエラーによって不可能になった場合に、代わりに表示するコンポーネントとして機能します。Nested Routesを組んでいる場合には、エラーになったルートモジュールの ErrorBoundary だけが表示され、それより外側のレイアウト(多くの場合はメニューなどのUI)は引き続き使用することができるメリットがあるため、Nested Routesを組む場合には細かく設定しておくとよいでしょう。

HydrateFallback

 コンポーネント区分の最後は、HydrateFallback です。これは、初期表示時にブラウザ側で実行されるloaderである clientLoader が完了するまでの間に、デフォルトコンポーネントの代わりに表示するコンポーネントを定義するためのものです。リスト4のように定義します。

[リスト4]app/routes/hydrate-fallback.jsx
import { useLoaderData } from "@remix-run/react";

// (1)
export async function clientLoader() {
  const url = import.meta.url;

  // Hydrate Fallbackを確認するために10秒待つ
  await new Promise((resolve) => setTimeout(resolve, 10000));

  return {
    url,
  }
}

// (2)
export function HydrateFallback() {
  return <p>Loading Browser Settings...</p>;
}

export default function Component() {
  const { url } = useLoaderData();

  return (
    <main>
      <h1>Hydrate Fallback</h1>
      <span>現在のURL: {url}</span>
    </main>
  );
}

 HydrateFallback が表示されたときに確認しやすいよう、(1)の clientLoader では、10秒間待機するようにしています。このコンポーネントを表す関数は、(2)の HydrateFallback として定義します。このコンポーネントは、初期表示時に clientLoader が完了するまでの間に表示されます。リスト4のように定義しておくと、初期表示時に図3の画面が表示されます。

図3:HydrateFallbackが表示される
図3:HydrateFallbackが表示される

 このまま10秒待つと、次は図4のように、HydrateFallback ではなく、デフォルトのコンポーネントが表示されます。

図4:デフォルトのコンポーネントが表示される
図4:デフォルトのコンポーネントが表示される

 実際には10秒も待つことはないと思いますが、もし clientLoader の待ち時間が長くなりそうな場合は、HydrateFallback を定義しておくと、ユーザーに「フリーズしているのではなく、必要な処理を行っているので待っていてほしい」ことを伝えることができます。通説ですが、200ミリ秒を超えるとユーザーが「待っている」と感じ始めるので、ひとつの目安にしてみてください。

 注意点として、loaderclientLoader を併用している場合は、clientLoader 関数に特殊なプロパティを定義しないと、HydrateFallback が表示されません。HydrateFallback を使いたい場合は、リスト5のように定義します。

[リスト5]app/routes/hydrate-fallback.jsx
export async function loader() {
 // (省略)
}
export async function clientLoader({ serverLoader }) {
  // (省略)
}
clientLoader.hydrate = true; // (1)

export function HydrateFallback() {
  // (省略)
}

 (1)のように、clientLoader 関数に hydrate = true というプロパティを定義しておくと、HydrateFallback が表示されるようになります。詳細は、公式ドキュメントを参照してください。

まとめ

 今回は、ルートモジュールに設定できる各種の設定のうち、コンポーネントやデータ授受に関するものを解説しました。アプリケーションの機能に関わるものが中心でしたね。

 次回はデータ授受に関する関数や、HTTPヘッダーの設定やHTMLの <head> 要素に関する設定などを中心に解説します。お楽しみに。

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Remixを通じてWebを学ぶ連載記事一覧

もっと読む

この記事の著者

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

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/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本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/19056 2024/03/01 11:00

イベント

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

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

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

メールバックナンバー