フェッチャーの使い方
このページの内容

フェッチャーの使用

フェッチャーは、ナビゲーションを起こさずに、複数同時データインタラクションを必要とする複雑で動的なユーザーインターフェースを作成するのに役立ちます。

フェッチャーは、独自の独立した状態を追跡し、データの読み込み、データの変更、フォームの送信、そして一般的にローダーとアクションとのインタラクションに使用できます。

アクションの呼び出し

フェッチャーの最も一般的なケースは、アクションにデータを送信し、ルートデータの再検証をトリガーすることです。次のルートモジュールを考えてみましょう。

import { useLoaderData } from "react-router";

export async function clientLoader({ request }) {
  let title = localStorage.getItem("title") || "No Title";
  return { title };
}

export default function Component() {
  let data = useLoaderData();
  return (
    <div>
      <h1>{data.title}</h1>
    </div>
  );
}

1. アクションを追加する

まず、フェッチャーが呼び出すためのアクションをルートに追加します。

import { useLoaderData } from "react-router";

export async function clientLoader({ request }) {
  // ...
}

export async function clientAction({ request }) {
  await new Promise((res) => setTimeout(res, 1000));
  let data = await request.formData();
  localStorage.setItem("title", data.get("title"));
  return { ok: true };
}

export default function Component() {
  let data = useLoaderData();
  // ...
}

2. フェッチャーを作成する

次に、フェッチャーを作成し、それを使ってフォームをレンダリングします。

import { useLoaderData, useFetcher } from "react-router";

// ...

export default function Component() {
  let data = useLoaderData();
  let fetcher = useFetcher();
  return (
    <div>
      <h1>{data.title}</h1>

      <fetcher.Form method="post">
        <input type="text" name="title" />
      </fetcher.Form>
    </div>
  );
}

3. フォームを送信する

ここでフォームを送信すると、フェッチャーはアクションを呼び出し、ルートデータを自動的に再検証します。

4. ペンディング状態をレンダリングする

フェッチャーは、非同期作業中に状態を利用できるようにするため、ユーザーが操作した瞬間にペンディングUIをレンダリングできます。

export default function Component() {
  let data = useLoaderData();
  let fetcher = useFetcher();
  return (
    <div>
      <h1>{data.title}</h1>

      <fetcher.Form method="post">
        <input type="text" name="title" />
        {fetcher.state !== "idle" && <p>Saving...</p>}
      </fetcher.Form>
    </div>
  );
}

5. オプティミスティックUI

フォームに次の状態をすぐにレンダリングするのに十分な情報がある場合があります。 `fetcher.formData` でフォームデータにアクセスできます。

export default function Component() {
  let data = useLoaderData();
  let fetcher = useFetcher();
  let title = fetcher.formData?.get("title") || data.title;

  return (
    <div>
      <h1>{title}</h1>

      <fetcher.Form method="post">
        <input type="text" name="title" />
        {fetcher.state !== "idle" && <p>Saving...</p>}
      </fetcher.Form>
    </div>
  );
}

6. フェッチャーデータとバリデーション

アクションから返されたデータは、フェッチャーの `data` プロパティで使用できます。これは、主に失敗したミューテーションのエラーメッセージをユーザーに返すのに役立ちます。

// ...

export async function clientAction({ request }) {
  await new Promise((res) => setTimeout(res, 1000));
  let data = await request.formData();

  let title = data.get("title") as string;
  if (title.trim() === "") {
    return { ok: false, error: "Title cannot be empty" };
  }

  localStorage.setItem("title", title);
  return { ok: true, error: null };
}

export default function Component() {
  let data = useLoaderData();
  let fetcher = useFetcher();
  let title = fetcher.formData?.get("title") || data.title;

  return (
    <div>
      <h1>{title}</h1>

      <fetcher.Form method="post">
        <input type="text" name="title" />
        {fetcher.state !== "idle" && <p>Saving...</p>}
        {fetcher.data?.error && (
          <p style={{ color: "red" }}>
            {fetcher.data.error}
          </p>
        )}
      </fetcher.Form>
    </div>
  );
}

データの読み込み

フェッチャーのもう1つの一般的なユースケースは、コンボボックスなどのルートからデータを読み込むことです。

1. 検索ルートを作成する

非常に基本的な検索を備えた次のルートを考えてみましょう。

// { path: '/search-users', filename: './search-users.tsx' }
const users = [
  { id: 1, name: "Ryan" },
  { id: 2, name: "Michael" },
  // ...
];

export async function loader({ request }) {
  await new Promise((res) => setTimeout(res, 300));
  let url = new URL(request.url);
  let query = url.searchParams.get("q");
  return users.filter((user) =>
    user.name.toLowerCase().includes(query.toLowerCase())
  );
}

2. コンボボックスコンポーネントでフェッチャーをレンダリングする

import { useFetcher } from "react-router";

export function UserSearchCombobox() {
  let fetcher = useFetcher();
  return (
    <div>
      <fetcher.Form method="get" action="/search-users">
        <input type="text" name="q" />
      </fetcher.Form>
    </div>
  );
}
  • アクションは、上記で作成したルート "/search-users" を指しています。
  • 入力の名前は、クエリパラメータと一致するように "q" です。

3. 型推論を追加する

import { useFetcher } from "react-router";
import type { Search } from "./search-users";

export function UserSearchCombobox() {
  let fetcher = useFetcher<typeof Search.action>();
  // ...
}

型のみをインポートするように `import type` を使用してください。

4. データをレンダリングする

import { useFetcher } from "react-router";

export function UserSearchCombobox() {
  let fetcher = useFetcher<typeof Search.action>();
  return (
    <div>
      <fetcher.Form method="get" action="/search-users">
        <input type="text" name="q" />
      </fetcher.Form>
      {fetcher.data && (
        <ul>
          {fetcher.data.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

結果を表示するには、"Enter" キーを押してフォームを送信する必要があることに注意してください。

5. ペンディング状態をレンダリングする

import { useFetcher } from "react-router";

export function UserSearchCombobox() {
  let fetcher = useFetcher<typeof Search.action>();
  return (
    <div>
      <fetcher.Form method="get" action="/search-users">
        <input type="text" name="q" />
      </fetcher.Form>
      {fetcher.data && (
        <ul
          style={{
            opacity: fetcher.state === "idle" ? 1 : 0.25,
          }}
        >
          {fetcher.data.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

6. ユーザー入力で検索する

フェッチャーは `fetcher.submit` でプログラム的に送信できます。

<fetcher.Form method="get" action="/search-users">
  <input
    type="text"
    name="q"
    onChange={(event) => {
      fetcher.submit(event.currentTarget.form);
    }}
  />
</fetcher.Form>

入力イベントのフォームは `fetcher.submit` の最初の引数として渡されることに注意してください。フェッチャーは、そのフォームを使用してリクエストを送信し、その属性を読み取り、その要素からデータをシリアル化します。

ドキュメントと例 CC 4.0