React Router v7がリリースされたので試してみた

HRBrain Advent Calendar 2024 19日目の記事です。

https://qiita.com/advent-calendar/2024/hrbrain

## はじめに
こんにちは。yk_engrです。
普段はバックエンドエンジニアをしていますが、
過去にRemix v2を軽く触ったことがあり、今回、React Router v7がReact RouterとRemixの機能を統合する形でリリースされたため、何が変わったのかを一通り触ってみようと思いました。

![remix_to_react_router.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253982/699ae10d-e822-f7c6-13bb-b7138060da8f.png)
>画像は以下のXの投稿から引用させていただきました: 
>https://x.com/jacobmparis/status/1790904647630151889


## 記事作成時のスタック
React Router v7
pnpm v9.12.2

## 参考
公式ドキュメントがベースですが、

https://reactrouter.com/home

ドキュメントの日本語版を作成してくださっている方がいたので、
こちらも参考にさせていただいてます。

https://react-router-docs-ja.techtalk.jp/

### ライブラリとしてのReact Router
以前のバージョンと同様に、シンプルで宣言的なルーティングライブラリとしても使用できますが、
今回は、フレームワークとしてのReact Routerを使用します。


## インストール&起動
以下のようにcreateで作成して、run devで起動します。
```shell_session
$ pnpm create react-router@pre my-app
$ cd my-app
$ pnpm i
$ pnpm run dev
```
ブラウザで`http://localhost:5173`を開けば起動していることが確認できます。


## ルーティング
デフォルトではコンフィグベースでのルーティングで、`app/routes.ts`ファイルに以下のような構成を書きます。
```typescript:app/route.ts
import { type RouteConfig, index, layout, prefix, route } from "@react-router/dev/routes";

export default [
  // root.tsxの <Outlet /> を配置した箇所でレンダリングされる
  index("routes/home.tsx"),
  route("about", "routes/about.tsx"),

  layout("layouts/auths/layout.tsx", [
    // auths/layout.tsx内の <Outlet /> を配置した箇所でレンダリングされる
    route("login", "routes/auths/login.tsx"),
    route("logout", "routes/auths/logout.tsx"),
  ]),

  layout("layouts/bases/layout.tsx", [
    // bases/layout.tsx内の <Outlet /> を配置した箇所でレンダリングされる
    route("profile", "routes/users/profile.tsx"),
  ]),

  ...prefix("todos", [
    // root.tsxの <Outlet /> を配置した箇所でレンダリングされる
    index("routes/todos/home.tsx"),
    route("detail", "routes/todos/detail.tsx")
  ]),

] satisfies RouteConfig;
```
Remixと同様に、`<Outlet />`内に、子ルートを配置し、レンダリングします。
```tsx:layouts/bases/layout.tsx
export default function Layout() {
  return (
    <>
      <div>基本のレイアウト</div>
      <Outlet />
    </>
   );
}
```

### ファイルベースルーティング
`@react-route/fs-routes`ライブラリを使えば、
Remix v2のようなファイルベースのルーティングも可能です。

## ローダー(データの読み込み)
データの読み込み時には、`loader`と`clientLoader`を使用します。
`loader`はサーバー側の初回レンダリング時にデータを取得し、`clientLoader`は後続のクライアント側のナビゲーションで使用します。
```tsx:routes/users/profile.tsx
import type { Route } from "./+types/profile";

export function loader() {
  return {
    name: "田中 太郎",
    gender: "男性",
    birthday: "2000/05/03"
  }
}

export default function Profile({ loaderData }: Route.ComponentProps) {
  return (
    <>
      <div>名前: {loaderData.name}</div>
      <div>性別: {loaderData.gender}</div>
      <div>誕生日: {loaderData.birthday}</div>
    </>
  );
}
```

## アクション(データの変更)
データの変更には、`action`と`clientAction`を使用します。
`action`はサーバーでのみ呼び出され、`clientLoader`はブラウザで実行されます。
```tsx
export async function action({
  request,
}: Route.ActionArgs) {
  let formData = await request.formData();
  let name = await formData.get("name");
  let profile = await fakeDb.updateProfile({ name });
  return project;
}
```

### アクションの呼び出し
アクションは、`<Form>`タグを使うか、`useSubmit`や`fetcher.submit`を使って、POSTメソッドが呼ばれた時に実行されます。


## ナビゲーション
`<Link>`、`<NavLink>`、`<Form>`、`redirect`、`useNavigate`を使用して、移動できます。
以下は、NavLinkコンポーネントの例です。
```tsx
export function MyAppNav() {
  return (
    <nav>
      <NavLink to="/" end>ホーム</NavLink>
      <NavLink to="/about" end>About</NavLink>
      <NavLink to="/profile" end>プロフィール</NavLink>
    </nav>
  )
}
```

### redirect
`loader`や`action`内でリダイレクトする場合は、以下のように書きます。
```tsx
export async function loader({ request }) {
  let profile = await getUserProfile(request);
  if (!user) {
    return redirect("/login");
  }
  return { name: profile.name };
}
```


## まとめ
過去にRemix v2を触った経験があったため、ローダーやアクションなどの実装の部分はスムーズに理解できましたが、一方で、フロントエンドの実装経験が少なかったこともあり、動かすのに少し時間がかかりました。今後、フロントエンドの実装を検討する際に、どのフレームワークやルーティング方法を導入するかの参考になればと思います。


## 参考リンク

https://zenn.dev/jiiyaaa/articles/a937b4a66a4bb6

https://qiita.com/t-kurasawa/items/50efb052f991334877d4

## PR
株式会社HRBrainでは新しいメンバーを募集中です。
興味がある方は下記のリンクからよろしくお願いいたします。

https://www.hrbrain.co.jp/recruit