Closed33

React Router 7キャッチアップ

筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

shadcn/ui

https://ui.shadcn.com/docs/installation/remix

% 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.
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei
% 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
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

client-side-routing

クライアント側ルーティングには、react-routeのLinkが使える
アプリはページ全体を再読み込みせずに URL を更新できます。
代わりに、アプリは新しい UI をすぐにレンダリングできます。

筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

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 フックで取得
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

SPA

  • 「シングルページアプリケーション (SPA)」では、初回に HTML と JavaScript がロードされ、その後はクライアントサイドでデータをロードして動的にコンテンツを表示する

CSRとSSR

CSR

  • クライアントロード (CSR)は、ページを開いた後、ブラウザがデータを取得してレンダリングする。初期ロードが若干遅い場合がありますが、設定がシンプルで軽量。

SSR

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

ハイブリッドレンダリング

一部 CSR、一部 SSRにすることもできる

筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

type-safety

アプリ内の各ルートに対して型を自動生成してくれる

// existing imports
import type { Route } from "./+types/root";
// existing imports & exports

export default function App({
  loaderData,
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

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>
	);
}
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

pre-rendering-a-static-route

  • 単なる静的ページでもCSRだと、リフレッシュするとローディング画面が表示されてしまうのは体験としてはよくない
  • react-router.config.tsにpre-renderingするページを指定することで、ローディングなしで表示可能
  • 上記を設定しても表示される場合は、clientLoaderが定義されていないかを確認する
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

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 };
}
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

data-mutations

  • ChatGPT解説↓

1. HTMLフォームとナビゲーション

HTMLフォームは、もともとブラウザにおけるデータ送信の基本的な仕組みとして機能しています。
React Routerでは、この「HTMLフォームによるナビゲーション」という考え方を取り入れ、データの操作(data mutation primitive)の基盤としています。

HTMLフォームの仕組み

HTMLフォームは、リンク(<a>タグなど)と同様にブラウザのナビゲーションを引き起こします
ただし、リンクはURLだけを変更するのに対し、フォームは以下のような追加機能があります:

  1. リクエストメソッドの変更
    • デフォルトではGETですが、POSTPUTなども使用できます。
  2. リクエストボディの送信
    • フォームの入力データが送信され、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でフォーム送信がどのように動作するかの概要です:

  1. フォームが送信される
    • <form>タグやuseSubmitフックを使用して送信します。
  2. データがシリアライズされる
    • ブラウザがフォームデータを適切な形式に変換します(URLSearchParamsやリクエストボディなど)。
  3. アクション関数にデータが送られる
    • React Routerは、フォーム送信のデータを指定された「アクション関数」に渡します。
  4. アクション関数で処理を実行
    • データベースの更新や状態の変更などを行います。
  5. 結果をレンダリング
    • 必要に応じて画面を更新します。

まとめ

React Routerは、HTMLフォームの従来の仕組みをうまく活用しつつ、クライアントサイドアプリのモダンなUXを実現しています。このアプローチにより、開発者はシンプルで直感的なコードを書きながら、効率的なデータ操作を行うことができます。

筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

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フォルダの各ファイル

って感じかなー

筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

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)を自動的に行うため、開発者が追加のコードを書く必要がなく、簡潔に実装できます。
筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

global-pending-ui

useNavigation returns the current navigation state: it can be one of "idle", "loading" or "submitting".

useNavigationを活用することで、画面遷移時にloading体験を改善できる

筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

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される模様

筧剛彰 / Takaaki Kakei筧剛彰 / Takaaki Kakei

cancel-button

ブラウザの履歴の 1 つのエントリが戻せる

const navigate = useNavigate();
..snip..
<button onClick={() => navigate(-1)} type="button">
このスクラップは2025/01/15にクローズされました