React Router 7キャッチアップ
npx create-react-router@latest . --package-manager pnpm

shadcn/ui
% pnpm dlx shadcn@latest init 2025/01/06 11:09:16
Packages: +170
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 170, reused 170, downloaded 0, added 170, done
✔ Preflight checks.
✔ Verifying framework. Found Vite.
✔ Validating Tailwind CSS.
✔ Validating import alias.
✔ Which style would you like to use? › Default
✔ Which color would you like to use as the base color? › Zinc
✔ Would you like to use CSS variables for theming? … no / yes
✔ Writing components.json.
✔ Checking registry.
✔ Updating tailwind.config.ts
✔ Updating app/app.css
✔ Installing dependencies.
✔ Created 1 file:
- app/lib/utils.ts
Success! Project initialization completed.
You may now add components.
create a postcss.config.js
pnpm dlx shadcn@latest add button
fs-routes
動的にパス生成してくれるやつの採用は一旦見送り
- 明示的にパスを指定したい
- shadcn/uiのthemeが聞かなくなる
チュートリアル
% npx create-react-router@latest --template remix-run/react-router/tutorials/address-book
create-react-router v7.1.1
dir Where should we create your new project?
./my-react-router-app
◼ Template: Using remix-run/react-router/tutorials/address-book...
✔ Template copied
git Initialize a new git repository?
Yes
deps Install dependencies with npm?
Yes
✔ Dependencies installed
✔ Git initialized
done That's it!
Enter your project directory using cd ./my-react-router-app
Check out README.md for development and deploy instructions.
Join the community at https://rmx.as/discord

client-side-routing
クライアント側ルーティングには、react-routeのLinkが使える
アプリはページ全体を再読み込みせずに URL を更新できます。
代わりに、アプリは新しい UI をすぐにレンダリングできます。
loading-data
- clientLoader は非同期データを返し、そのデータは React Router によって自動的に現在のルートのコンポーネントに渡される
- ルートの設定時に clientLoader を指定した場合、その結果がルートコンポーネントの props.loaderData に注入されます。
export async function clientLoader() {
const contacts = await getContacts();
return { contacts };
}
export default function App({ loaderData }) {
const { contacts } = loaderData;
- React Router では、loaderData を直接受け取る方法以外にも、useLoaderData フックを利用する方法がある
export async function clientLoader() {
const contacts = await getContacts();
return { contacts };
}
export default function App() {
const { contacts } = useLoaderData(); // useLoaderData フックで取得
SPA
- 「シングルページアプリケーション (SPA)」では、初回に HTML と JavaScript がロードされ、その後はクライアントサイドでデータをロードして動的にコンテンツを表示する
CSRとSSR
CSR
- クライアントロード (CSR)は、ページを開いた後、ブラウザがデータを取得してレンダリングする。初期ロードが若干遅い場合がありますが、設定がシンプルで軽量。

SSR
- サーバーサイドレンダリング (SSR)は、サーバー側でデータを取得してレンダリングした HTML をクライアントに送信する。初期表示が速く、SEO に有利です。ただし、サーバーの設定が必要になる。

ハイブリッドレンダリング
一部 CSR、一部 SSRにすることもできる

type-safety
アプリ内の各ルートに対して型を自動生成してくれる
// existing imports
import type { Route } from "./+types/root";
// existing imports & exports
export default function App({
loaderData,
adding-a-hydratefallback
- 「白いフラッシュ (FOW)」は、CSR の典型的な問題
- hydratefallbackを使って、Loading画面を表示することで、改善可能
LayoutはReact Router v7 の新しいコンセプトで、定義したHydrateFallbackやErrorBoundaryが自動反映されると一旦理解した
export function HydrateFallback() {
return (
<div id="loading-splash">
<div id="loading-splash-spinner" />
<p>Loading, please wait...</p>
</div>
);
}
layout-routes
サイドバーなどのレイアウトはlayoutフォルダに定義するのがよさげ
pre-rendering-a-static-route
- 単なる静的ページでもCSRだと、リフレッシュするとローディング画面が表示されてしまうのは体験としてはよくない
- react-router.config.tsにpre-renderingするページを指定することで、ローディングなしで表示可能
- 上記を設定しても表示される場合は、clientLoaderが定義されていないかを確認する
url-params-in-loaders
URLパラメータ情報は、loaderのparams引数で取得可能
export async function loader({ params }: Route.LoaderArgs) {
const contact = await getContact(params.contactId);
return { contact };
}
型
export async function loader({ params }: Route.LoaderArgs) {
const contact = await getContact(params.contactId);
if (!contact) {
throw new Response("Not Found", { status: 404 });
}
return { contact };
}
data-mutations
- ChatGPT解説↓
1. HTMLフォームとナビゲーション
HTMLフォームは、もともとブラウザにおけるデータ送信の基本的な仕組みとして機能しています。
React Routerでは、この「HTMLフォームによるナビゲーション」という考え方を取り入れ、データの操作(data mutation primitive)の基盤としています。
HTMLフォームの仕組み
HTMLフォームは、リンク(<a>タグなど)と同様にブラウザのナビゲーションを引き起こします。
ただし、リンクはURLだけを変更するのに対し、フォームは以下のような追加機能があります:
-
リクエストメソッドの変更
- デフォルトでは
GETですが、POSTやPUTなども使用できます。
- デフォルトでは
-
リクエストボディの送信
- フォームの入力データが送信され、
POSTリクエストの場合はリクエストボディに含まれます。GETの場合はURLのクエリパラメータとして追加されます。
- フォームの入力データが送信され、
2. 従来のブラウザとReact Routerの動作の違い
従来のブラウザ
- フォームを送信すると、ブラウザがデータを自動的にシリアライズ(形式変換)し、サーバーにリクエストを送ります。
- GETの場合: フォームデータはURLクエリパラメータとして送信されます。
- POSTの場合: フォームデータはリクエストボディに含まれて送信されます。
React Routerの場合
- React Routerでは、フォーム送信はブラウザナビゲーションを模倣しますが、クライアントサイドで処理される点が異なります。
- フォームデータはシリアライズされますが、サーバーに送信するのではなく、React Routerの「アクション関数」に渡されます。
3. React Routerのアプローチの利点
React Routerは、従来の「サーバーサイド送信」モデルのシンプルさを活かしながら、クライアントサイドアプリケーションの利便性を組み合わせています。
利便性のポイント
- 「リンクのようなフォーム」: フォーム送信をリンクのクリックと同じように扱えるため、直感的です。
- 「クライアントサイド処理」: データをサーバーに送る代わりに、アプリケーション内で処理できるため、ページ全体のリロードが不要です。
- 「データの操作」: フォームを使ってReact Routerのアクションにデータを渡すことで、簡単にデータを更新したり、状態を管理したりできます。
4. React Routerでのフォーム送信フロー
以下がReact Routerでフォーム送信がどのように動作するかの概要です:
-
フォームが送信される
-
<form>タグやuseSubmitフックを使用して送信します。
-
-
データがシリアライズされる
- ブラウザがフォームデータを適切な形式に変換します(
URLSearchParamsやリクエストボディなど)。
- ブラウザがフォームデータを適切な形式に変換します(
-
アクション関数にデータが送られる
- React Routerは、フォーム送信のデータを指定された「アクション関数」に渡します。
-
アクション関数で処理を実行
- データベースの更新や状態の変更などを行います。
-
結果をレンダリング
- 必要に応じて画面を更新します。
まとめ
React Routerは、HTMLフォームの従来の仕組みをうまく活用しつつ、クライアントサイドアプリのモダンなUXを実現しています。このアプローチにより、開発者はシンプルで直感的なコードを書きながら、効率的なデータ操作を行うことができます。
creating-contacts
- Form送信すると、ブラウザのデフォルトのフォーム送信を防ぎ、代わりにReact Routerのaction関数にリクエストを僧院する。この仕組みにより、クライアントサイドでJSを使ってデータ送信が行われる。
- POSTリクエストはデータ変更を意味するとみなされます。そのため、React Routerはアクション関数が完了した後、自動的にページ上のデータをリバリデート(再検証)します。これにより、明示的にデータの再取得コードを書く必要がなくなります。
- React Routerの仕組みはHTMLとHTTPに基づいているため、JavaScriptを無効化しても機能します。
export async function action() {
const contact = await createEmptyContact();
return { contact };
}
疑問:アクション関数はrootページで定義するべきなのか
- 共通利用は、rootページのファイル
- 個別利用は、routesフォルダの各ファイル
って感じかなー
mutation-discussion
request.formData()でフォームデータを取得できる
export async function action({ params, request }: Route.ActionArgs) {
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateContact(params.contactId, updates);
return redirect(`/contacts/${params.contactId}`);
}
const formData = await request.formData();
const firstName = formData.get("first");
const lastName = formData.get("last");
クライアントサイドルーティングとリダイレクトの違い
- サーバーサイドリダイレクト
- 通常、サーバーサイドリダイレクトでは、リダイレクト後の新しいページで完全に新しいHTTPリクエストが発生します。その結果、ブラウザはページ全体をリロードし、サーバーから最新のデータを取得してレンダリングします。
- クライアントサイドリダイレクト(React Routerの場合)
- React Routerでは、リダイレクトがクライアントサイドで行われるため、ブラウザ全体のリロードは発生しません。代わりに、React Routerが自動的に現在のページのデータを「再検証(revalidate)」して、必要に応じて最新の情報を取得します。
- JavaScriptが無効の場合
- JavaScriptが無効である場合、リダイレクトは通常のHTTPリダイレクトとして処理され、ページ全体が再ロードされます。
- JavaScriptが有効であれば、React Routerがクライアント側でリダイレクトを処理し、スクロール位置やコンポーネントの状態を維持できます。
React Routerの利点
- React Routerはサーバーサイドリダイレクトと同じ振る舞いをエミュレートしつつ、クライアントサイドルーティングの利点(状態の保持、効率的なデータフェッチ)を活かしています。
- リダイレクト後のデータ再検証(Revalidation)を自動的に行うため、開発者が追加のコードを書く必要がなく、簡潔に実装できます。
global-pending-ui
useNavigation returns the current navigation state: it can be one of "idle", "loading" or "submitting".
useNavigationを活用することで、画面遷移時にloading体験を改善できる
deleting-records
フォームの送信先を指定します。destroyという相対パスが指定されており、現在のルートからの相対的なURLに解釈されます。例えば、ルートがcontacts/:contactIdの場合、contacts/:contactId/destroyにリクエストが送信されます。
<Form
action="destroy"
method="post"
onSubmit={(event) => {
const response = confirm(
"Please confirm you want to delete this record."
);
if (!response) {
event.preventDefault();
}
}}
>
<button type="submit">Delete</button>
</Form>
onSubmitの処理が終わってからPostされる模様
cancel-button
ブラウザの履歴の 1 つのエントリが戻せる
const navigate = useNavigate();
..snip..
<button onClick={() => navigate(-1)} type="button">