フェッチャーは、ナビゲーションを起こさずに、複数同時データインタラクションを必要とする複雑で動的なユーザーインターフェースを作成するのに役立ちます。
フェッチャーは、独自の独立した状態を追跡し、データの読み込み、データの変更、フォームの送信、そして一般的にローダーとアクションとのインタラクションに使用できます。
フェッチャーの最も一般的なケースは、アクションにデータを送信し、ルートデータの再検証をトリガーすることです。次のルートモジュールを考えてみましょう。
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>
);
}
まず、フェッチャーが呼び出すためのアクションをルートに追加します。
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();
// ...
}
次に、フェッチャーを作成し、それを使ってフォームをレンダリングします。
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>
);
}
ここでフォームを送信すると、フェッチャーはアクションを呼び出し、ルートデータを自動的に再検証します。
フェッチャーは、非同期作業中に状態を利用できるようにするため、ユーザーが操作した瞬間にペンディング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>
);
}
フォームに次の状態をすぐにレンダリングするのに十分な情報がある場合があります。 `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>
);
}
アクションから返されたデータは、フェッチャーの `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つの一般的なユースケースは、コンボボックスなどのルートからデータを読み込むことです。
非常に基本的な検索を備えた次のルートを考えてみましょう。
// { 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())
);
}
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>
);
}
import { useFetcher } from "react-router";
import type { Search } from "./search-users";
export function UserSearchCombobox() {
let fetcher = useFetcher<typeof Search.action>();
// ...
}
型のみをインポートするように `import type` を使用してください。
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" キーを押してフォームを送信する必要があることに注意してください。
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>
);
}
フェッチャーは `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` の最初の引数として渡されることに注意してください。フェッチャーは、そのフォームを使用してリクエストを送信し、その属性を読み取り、その要素からデータをシリアル化します。