React Router
https://reactrouter.com/en/main
Remix と同じチームが開発しており、Remix でも用いられている
インストール
code:sh
$ npm install react-router react-router-dom
V6
従来の BroserRouter ではなく createBrowserRouter を用いて以下のようにルーティングを定義することで、Data APIs が利用可能に
https://reactrouter.com/en/main/routers/picking-a-router#data-apis
なので困ったら createBrowserRouter を使うのが良さそう radish-miyazaki.icon
ただ、この Data APIs の学習コストが高い… radish-miyazaki.icon
code:tsx
const router = createBrowserRouter([
{ path: "/signin", element: <GuestRoute children={<SignIn />} /> },
{ path: "/signup", element: <GuestRoute children={<SignUp />} /> },
{ path: "", element: <PrivateRoute children={<Main />} /> },
{ path: "*", element: <NotFound /> },
]);
function App() {
return <RouterProvider router={router} />;
}
Data APIs
https://reactrouter.com/en/main/routers/picking-a-router#data-apis
データフェッチやミューテーションを効率的に行うための機能(API)
ルーティングとデータ管理がより密接に統合され、データ駆動 型のアプリケーションを構築できるようになった
主な機能
Loader: 特定のルートがレンダリングされる前にデータを取得するための非同期関数
https://reactrouter.com/en/main/route/loader#loader
ルート定義で loaderプロパティを設定し、その中でデータフェッチを行う
code:tsx
function loader() {
return fetchFakeAlbums();
}
const router = createBrowserRouter([
{
path: "/",
loader: loader,
element: <Albums />,
},
]);
取得したデータは、useLoaderData フックでアクセスできる
https://reactrouter.com/en/main/hooks/use-loader-data#useloaderdata
code:tsx
export function Albums() {
const albums = useLoaderData();
// ...
}
Action: フォーム送信やユーザ操作に応じてデータ更新するための非同期関数
https://reactrouter.com/en/main/route/action
ルート定義で action プロパティを設定し、その中でデータ更新処理を行う
code:tsx
{
path: "/users/new",
element: <UserForm />,
action: async ({ request }) => {
const formData = await request.formData();
const response = await fetch("/api/users", {
method: "POST",
body: formData,
});
return response.json();
},
}
function UserForm() {
const actionData = useActionData();
return (
<Form method="post">
<button type="submit">作成</button>
</Form>
);
}
フォームには、<Form> コンポーネントを用いる
https://reactrouter.com/en/main/components/form
Props
action
フォームが送信される URL を指定する
省略した場合、現在のルートの action が呼び出される
method
HTML メソッドを指定する
デフォルトは GET
<form> とは異なり、PUT や PATCH、DELETE もサポートする
useSubmit を用いると、ユーザの代わりにフォーム送信を行うことができる
https://reactrouter.com/en/main/hooks/use-submit
code:tsx
function SearchField() {
let submit = useSubmit();
return (
<Form
onChange={(event) => {
submit(event.currentTarget);
}}
<input type="text" name="search" />
<button type="submit">Search</button>
</Form>
);
}
処理結果は useActionData フックでアクセスできる
https://reactrouter.com/en/main/hooks/use-action-data
code:tsx
function SomeComponent() {
let actionData = useActionData();
// ...
}
useFetcher: 特定のルートに依存せずデータフェッチやミューテーションを行うためのフック ??
https://reactrouter.com/en/main/hooks/use-fetcher
code:tsx
function Search() {
const fetcher = useFetcher();
return (
<div>
<fetcher.Form action="/api/search" method="get">
<input name="query" type="text" />
<button type="submit">検索</button>
</fetcher.Form>
{fetcher.data && <SearchResults results={fetcher.data} />}
</div>
);
}
ページ遷移を伴わないデータ操作や、ルート外のコンポーネントからの action や loader を呼び出すことが可能
ページ遷移を伴わないデータ操作
e.g. 検索フォーム、いいねボタン
コンポーネント
fetcher.Form: https://reactrouter.com/en/main/hooks/use-fetcher#fetcherform
フォームを作成し、action で特定のエンドポイントを指定できる
<Form> とは異なりページ遷移が発生しない
Just like <Form> except it doesn't cause a navigation.
<Form navigator={false}> で同じことが実現できる
https://reactrouter.com/en/main/components/form#navigate
You can tell the form to skip the navigation and use a fetcher internally by specifying <Form navigate={false}>.
This is essentially a shorthand for useFetcher() + <fetcher.Form> where you don't care about the resulting data and only want to kick off a submission and access the pending state via useFetchers().
メソッド
fetcher.load: https://reactrouter.com/en/main/hooks/use-fetcher#fetcherloadhref-options
GET リクエストを送信してデータを取得する
code:tsx
useEffect(() => {
fetcher.load("/api/data");
}, []);
fetcher.submit: https://reactrouter.com/en/main/hooks/use-fetcher#fetchersubmit
フォームデータを送信して、POST や PUT リクエストを行う
code:tsx
const handleSubmit = (event) => {
event.preventDefault();
fetcher.submit(event.target);
};
プロパティ
fetch.state: https://reactrouter.com/en/main/hooks/use-fetcher#fetcherstate
リクエストの状態を関するためのプロパティ
idle、submitting、loading のいずれか
code:tsx
if (fetcher.state === "loading") {
return <div>Loading...</div>;
}
fetch.data: https://reactrouter.com/en/main/hooks/use-fetcher#fetcherdata
action や loader から返されたデータを保持するためのプロパティ
Loader や Action の処理状態は useNavigation の state で取得することが可能
https://reactrouter.com/en/main/hooks/use-navigation#navigationstate
code:tsx
const navigation = useNavigation();
const text =
navigation.state === "submitting"
? "Saving..."
: navigation.state === "loading"
? "Saved!"
: "Go";
エラーハンドリング
https://reactrouter.com/en/main/route/error-element
Error Boundary と useRouteError フックを利用して、ルートごとのエラーハンドリングが可能
https://reactrouter.com/en/main/hooks/use-route-error
これにより、loader や action、ルートコンポーネント内でエラーが発生した際に指定したエラーページを表示できる
code:tsx
<Route
path="/invoices/:id"
loader={loadInvoice}
action={updateInvoice}
element={<Invoice />}
errorElement={<ErrorBoundary />}
/>;
function Invoice() {
return <div>Happy {path}</div>;
}
function ErrorBoundary() {
let error = useRouteError();
console.error(error);
return <div>Dang!</div>;
}
現在のメッセージでエラーメッセージを表示したい場合は、Loader / Action の戻り値でエラーを返して、それを useLoader やuseActionData で受け取れば良さそう? radish-miyazaki.icon
#React