Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

React Router と Remixで実現するモダンな画面遷移:初心者からプロまで使えるガイド

記事のサムネイル
TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

React Routerとは?基本概念と導入の利点

Webアプリケーション開発において、ページ間の遷移とルーティングは重要な要素です。React Routerは、Reactアプリケーションでこのルーティングを実現するための標準的なライブラリです。

React Routerの基本概念

React Routerを使うと、ユーザーがアプリケーション内のさまざまなページに移動する際、ブラウザの履歴を操作することなく、シングルページアプリケーション(SPA)としてスムーズな遷移を実現できます。

基本的なコンポーネントには以下のようなものがあります:

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">ホーム</Link>
        <Link to="/about">会社概要</Link>
        <Link to="/contact">お問い合わせ</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </BrowserRouter>
  );
}

React Routerの利点

React Routerを導入するメリットは多岐にわたります:

  1. 宣言的なルーティング: JSX構文を使って直感的にルートを定義できます
  2. コード分割の容易さ: 特定のルートでのみ必要なコードを動的にロードできます
  3. URLパラメータの活用: 動的なルーティングで柔軟なナビゲーションが可能です
  4. 履歴管理: ブラウザの履歴を適切に管理し、戻る・進むボタンの動作を制御できます

React Router バージョン6の新機能

最新のReact Router v6では、以前のバージョンと比較して多くの改善が行われています:

// 新しいデータAPIの使用例
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: "contacts/:contactId",
        element: <Contact />
      }
    ]
  }
]);

ReactDOM.createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);

このように、React Routerは進化を続けており、より宣言的で使いやすいAPIを提供しています。特にRemixフレームワークの影響を受けた新しいデータAPIは、より構造化されたルーティングを可能にしています。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

Remixフレームワークの特徴とReact Routerとの関係性

Remixは、React Routerの開発者たちが作成した、React Routerを深く統合したフルスタックのWebフレームワークです。Remixはルーティングだけでなく、データ取得、フォーム処理、エラーハンドリングなど、モダンなWebアプリケーション開発に必要な多くの機能を提供します。

Remixの主な特徴

Remixは以下のような特徴を持っています:

  1. ネストされたルーティング: フォルダベースのルーティングシステムを採用し、UIのネスト構造とURLの構造を一致させることができます

  2. サーバーサイドレンダリング: 各ルートで必要なデータをサーバーサイドで取得し、HTMLとしてレンダリングすることで、初期ロード時のパフォーマンスを向上させます

  3. プログレッシブエンハンスメント: JavaScriptが無効でも基本的な機能が動作するWebアプリケーションを構築できます

  4. 自動コード分割: ルート単位でのコード分割が自動的に行われ、必要なコードのみをロードします

React RouterとRemixの関係

RemixはReact Routerの拡張と考えることができます。実際、React Router v6.4以降には、Remixからインスピレーションを得た機能が多く取り入れられています。

// React Router v6.4以降のデータAPIの例
import { createBrowserRouter, Form, useLoaderData } from "react-router-dom";

// データローダー関数(Remixの影響)
async function contactLoader({ params }) {
  const contact = await getContact(params.contactId);
  return { contact };
}

function Contact() {
  // useLoaderDataでデータを取得(Remixの影響)
  const { contact } = useLoaderData();
  
  return (
    <div>
      <h1>{contact.name}</h1>
      
      {/* Formコンポーネント(Remixの影響) */}
      <Form method="post">
        <button type="submit">削除</button>
      </Form>
    </div>
  );
}

const router = createBrowserRouter([
  {
    path: "contacts/:contactId",
    element: <Contact />,
    loader: contactLoader,  // データローダーの設定
    action: contactAction,  // フォームアクションの設定
  }
]);

Remixの独自の利点

Remixには、React Routerにはない独自の利点もあります:

  1. アダプター: 様々なホスティング環境(Vercel, Netlify, AWS Lambda など)に対応するアダプターを提供しています

  2. サーバーとクライアントのコード共有: 同じリポジトリでサーバーとクライアントのコードを書くことができます

  3. エラー境界の自動化: ルート単位でのエラー境界を自動的に設定し、エラーハンドリングを容易にします

Remixは「Web基礎に立ち戻る」という考え方を持ちながらも、モダンな開発体験を提供する点で、フロントエンド開発の新しいアプローチを示しています。

あわせて読みたい

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

React Routerの基本実装:ルート設定とナビゲーション

React Routerを使ったアプリケーションの基本的な実装方法を見ていきましょう。まずは、プロジェクトに必要なパッケージをインストールします。

# React Router v6をインストール
npm install react-router-dom

基本的なルート設定

React Routerには大きく分けて2つの実装アプローチがあります:

1. コンポーネントベースのアプローチ(従来の方法)

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

2. データAPIを使ったアプローチ(推奨される新しい方法)

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
    errorElement: <NotFound />
  },
  {
    path: "about",
    element: <About />
  }
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

ナビゲーションの実装

ページ間のナビゲーションには、主に以下の方法があります:

import { Link } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <ul>
        <li><Link to="/">ホーム</Link></li>
        <li><Link to="/about">会社概要</Link></li>
        <li><Link to="/products">製品情報</Link></li>
      </ul>
    </nav>
  );
}
import { NavLink } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <ul>
        {/* activeClassNameを使ってアクティブなリンクのスタイルを変更 */}
        <li>
          <NavLink 
            to="/" 
            className={({ isActive }) => isActive ? "active-link" : ""}
          >
            ホーム
          </NavLink>
        </li>
        <li>
          <NavLink 
            to="/about" 
            className={({ isActive }) => isActive ? "active-link" : ""}
          >
            会社概要
          </NavLink>
        </li>
      </ul>
    </nav>
  );
}

3. useNavigateフックを使用したプログラムによるナビゲーション

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();
  
  const handleSubmit = async (event) => {
    event.preventDefault();
    // ログイン処理
    const success = await loginUser(formData);
    
    if (success) {
      // ログイン成功時にダッシュボードページへ遷移
      navigate('/dashboard');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {/* フォームの内容 */}
      <button type="submit">ログイン</button>
    </form>
  );
}

動的ルートの実装

URLパラメータを使って動的なルートを実装することもできます:

// ルート定義
<Route path="users/:userId" element={<UserProfile />} />

// または、データAPIの場合
const router = createBrowserRouter([
  {
    path: "users/:userId",
    element: <UserProfile />
  }
]);

// UserProfileコンポーネント内でパラメータを取得
import { useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  
  // userIdを使ってユーザー情報を取得
  
  return (
    <div>
      <h1>ユーザープロファイル</h1>
      <p>ユーザーID: {userId}</p>
      {/* ユーザー情報の表示 */}
    </div>
  );
}

これらの基本実装を理解することで、React Routerを使った効率的なルーティングが可能になります。次のセクションでは、RemixのLoader機能を活用したデータ取得について説明します。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

データ取得とローディング:RemixのLoader機能を活用する

Remixおよび最新のReact Routerでは、データ取得とローディング状態の管理が大幅に改善されています。特にRemixの「loader」という概念は、ルート単位でのデータ取得を非常に簡単にします。

Loaderの基本概念

Loaderは各ルートコンポーネントがレンダリングされる前に実行される関数で、コンポーネントに必要なデータを取得します。

Remixでのloader実装

// app/routes/users.$userId.jsx (Remixのファイル規約に従ったルートファイル)
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';

// Loaderはサーバーサイドで実行される関数
export async function loader({ params }) {
  const userId = params.userId;
  const user = await getUserById(userId);
  
  if (!user) {
    throw new Response("ユーザーが見つかりません", { status: 404 });
  }
  
  return json({ user });
}

// ルートコンポーネント
export default function UserProfile() {
  // useLoaderDataでloaderから返されたデータを取得
  const { user } = useLoaderData();
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>メール: {user.email}</p>
      <p>登録日: {new Date(user.createdAt).toLocaleDateString()}</p>
    </div>
  );
}

React Router v6.4以降でのloader実装

// React RouterでのLoader実装
import { useLoaderData } from 'react-router-dom';

// データ取得用のLoaderを定義
async function userLoader({ params }) {
  const userId = params.userId;
  const response = await fetch(`/api/users/${userId}`);
  
  if (!response.ok) {
    throw new Response("ユーザーが見つかりません", { status: response.status });
  }
  
  return response.json();
}

// ルートコンポーネント
function UserProfile() {
  const { user } = useLoaderData();
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>メール: {user.email}</p>
    </div>
  );
}

// ルート設定
const router = createBrowserRouter([
  {
    path: "users/:userId",
    element: <UserProfile />,
    loader: userLoader
  }
]);

並列データローディング

Remixでは、ネストされたルートの場合、すべてのローダーは並列に実行されます。これにより、データ取得のパフォーマンスが向上します。

// app/root.jsx (レイアウトルート)
export async function loader() {
  return json({
    user: await getAuthenticatedUser(),
    siteConfig: await getSiteConfig()
  });
}

// app/routes/dashboard.jsx (ダッシュボードルート)
export async function loader() {
  return json({
    stats: await getDashboardStats(),
    recentActivity: await getRecentActivity()
  });
}

// app/routes/dashboard.activity.jsx (アクティビティサブルート)
export async function loader() {
  return json({
    activityDetails: await getActivityDetails()
  });
}

上記の例では、ユーザーが /dashboard/activity にアクセスすると、3つのローダーが並列に実行されます。

ロード中の状態管理

Remixでのロード状態の管理

// app/routes/users.$userId.jsx
import { useLoaderData, useTransition } from '@remix-run/react';

export default function UserProfile() {
  const { user } = useLoaderData();
  const transition = useTransition();
  const isLoading = transition.state === "loading";
  
  return (
    <div>
      {isLoading ? (
        <div>読み込み中...</div>
      ) : (
        <>
          <h1>{user.name}</h1>
          <p>メール: {user.email}</p>
        </>
      )}
    </div>
  );
}

React Router v6.4以降でのロード状態の管理

import { useLoaderData, useNavigation } from 'react-router-dom';

function UserProfile() {
  const { user } = useLoaderData();
  const navigation = useNavigation();
  const isLoading = navigation.state === "loading";
  
  return (
    <div>
      {isLoading ? (
        <div>読み込み中...</div>
      ) : (
        <>
          <h1>{user.name}</h1>
          <p>メール: {user.email}</p>
        </>
      )}
    </div>
  );
}

データの再検証

Remixでは、ユーザーの操作に応じてデータを再検証する仕組みも提供されています:

// app/routes/dashboard.jsx
import { useLoaderData, useFetcher } from '@remix-run/react';

export default function Dashboard() {
  const { stats } = useLoaderData();
  const fetcher = useFetcher();
  
  // 手動でデータを再フェッチする関数
  const refreshStats = () => {
    fetcher.load('/dashboard');
  };
  
  return (
    <div>
      <h1>ダッシュボード</h1>
      <button onClick={refreshStats}>
        更新 {fetcher.state === "loading" && "..."}
      </button>
      
      <div>
        <h2>統計情報</h2>
        <p>アクセス数: {stats.totalVisits}</p>
        <p>コンバージョン率: {stats.conversionRate}%</p>
      </div>
    </div>
  );
}

これらのLoader機能を活用することで、データ取得とUI表示の分離が可能になり、よりメンテナンスしやすく、パフォーマンスの高いアプリケーションを構築できます。また、SEO的にも優れたアプリケーションを作成できるため、特にコンテンツ重視のサイトで大きなメリットがあります。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

関連記事

ユーザー体験を向上させるネストルーティングとレイアウト

ユーザー体験を向上させるためには、ネストされたルーティングとレイアウトの実装が重要です。これにより、共通のナビゲーションやレイアウト要素を維持しながら、ページの一部だけを更新することができます。

ネストルーティングの基本

ネストルーティングを使うと、URL構造とUIの階層を一致させることができます。例えば、ダッシュボードアプリケーションでは、以下のようなURL構造があるかもしれません:

  • /dashboard - ダッシュボードのメイン画面
  • /dashboard/stats - 統計情報画面
  • /dashboard/profile - ユーザープロファイル画面

React Routerでのネストルーティング

// 従来のComponent APIを使用した場合
<BrowserRouter>
  <Routes>
    <Route path="/dashboard" element={<DashboardLayout />}>
      <Route index element={<DashboardHome />} />
      <Route path="stats" element={<Stats />} />
      <Route path="profile" element={<Profile />} />
    </Route>
  </Routes>
</BrowserRouter>

// DashboardLayoutコンポーネント
import { Outlet } from 'react-router-dom';

function DashboardLayout() {
  return (
    <div className="dashboard-layout">
      <aside>
        <nav>
          <Link to="/dashboard">ホーム</Link>
          <Link to="/dashboard/stats">統計</Link>
          <Link to="/dashboard/profile">プロファイル</Link>
        </nav>
      </aside>
      
      <main>
        {/* 子ルートの内容がここに表示される */}
        <Outlet />
      </main>
    </div>
  );
}

新しいData APIを使用した場合

const router = createBrowserRouter([
  {
    path: "/dashboard",
    element: <DashboardLayout />,
    children: [
      {
        index: true,
        element: <DashboardHome />,
      },
      {
        path: "stats",
        element: <Stats />,
      },
      {
        path: "profile",
        element: <Profile />,
      },
    ],
  },
]);

Remixでのネストルーティング

Remixでは、ファイルシステムベースのルーティングを採用しており、ディレクトリ構造がそのままURLの階層構造になります:

app/
├── root.jsx
└── routes/
    ├── dashboard.jsx
    ├── dashboard/
    │   ├── index.jsx
    │   ├── stats.jsx
    │   └── profile.jsx
    └── ... その他のルート

このファイル構造は以下のようなURLにマッピングされます:

  • /dashboard - dashboard.jsx + dashboard/index.jsx
  • /dashboard/stats - dashboard.jsx + dashboard/stats.jsx
  • /dashboard/profile - dashboard.jsx + dashboard/profile.jsx

レイアウトは dashboard.jsx で定義します:

// app/routes/dashboard.jsx
import { Outlet } from '@remix-run/react';

export default function DashboardLayout() {
  return (
    <div className="dashboard-layout">
      <aside>
        <nav>
          <Link to="/dashboard">ホーム</Link>
          <Link to="/dashboard/stats">統計</Link>
          <Link to="/dashboard/profile">プロファイル</Link>
        </nav>
      </aside>
      
      <main>
        <Outlet />
      </main>
    </div>
  );
}

レイアウトパターンの活用

レイアウトパターンを活用することで、より整理されたコード構造を実現できます。例えば、複数のレイアウトを入れ子にすることも可能です:

// React Routerの場合
<Routes>
  <Route path="/" element={<RootLayout />}>
    <Route path="dashboard" element={<DashboardLayout />}>
      <Route index element={<DashboardHome />} />
      <Route path="stats" element={<Stats />} />
    </Route>
    <Route path="settings" element={<SettingsLayout />}>
      <Route index element={<GeneralSettings />} />
      <Route path="profile" element={<ProfileSettings />} />
    </Route>
  </Route>
</Routes>

// RootLayoutコンポーネント
function RootLayout() {
  return (
    <div className="app">
      <header>
        <MainNav />
      </header>
      
      <div className="content">
        <Outlet />
      </div>
      
      <footer>
        <Footer />
      </footer>
    </div>
  );
}

パスレスレイアウト

時にはパスに関連付けないレイアウト要素が必要な場合もあります。React Router v6.4以降では、パスレスのレイアウトルートを作成できます:

<Routes>
  <Route element={<AuthLayout />}>
    <Route path="login" element={<Login />} />
    <Route path="register" element={<Register />} />
  </Route>
</Routes>

// AuthLayoutコンポーネント
function AuthLayout() {
  return (
    <div className="auth-container">
      <div className="auth-form">
        <Outlet />
      </div>
      <div className="auth-info">
        {/* 認証画面の補足情報 */}
      </div>
    </div>
  );
}

動的なレイアウト

条件に基づいて異なるレイアウトを適用することも可能です:

function AppLayout() {
  const { user } = useAuth();
  
  return (
    <div className="app">
      <header>
        <MainNav />
      </header>
      
      <div className="content">
        {user ? (
          // ログイン済みユーザー向けのサイドバー付きレイアウト
          <div className="with-sidebar">
            <aside>
              <UserNav />
            </aside>
            <main>
              <Outlet />
            </main>
          </div>
        ) : (
          // 未ログインユーザー向けのシンプルレイアウト
          <main className="full-width">
            <Outlet />
          </main>
        )}
      </div>
      
      <footer>
        <Footer />
      </footer>
    </div>
  );
}

これらのネストルーティングとレイアウトパターンを活用することで、一貫性のあるユーザー体験を提供しながら、効率的な画面遷移を実現できます。特に、大規模なアプリケーションでは、適切なレイアウト設計が重要となります。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

エラーハンドリングとフォーム送信:Remixの実践的なアプローチ

Remixは、エラーハンドリングとフォーム送信に対する実践的なアプローチを提供しています。これらの機能は、React Router v6.4以降にも部分的に取り入れられています。

エラーハンドリング

Remixでのエラーハンドリング

Remixでは、各ルートにErrorBoundaryコンポーネントを定義することができます:

// app/routes/users.$userId.jsx
import { json } from '@remix-run/node';
import { useLoaderData, useCatch } from '@remix-run/react';

// Loaderでエラーをスローする
export async function loader({ params }) {
  const user = await getUserById(params.userId);
  
  if (!user) {
    throw new Response("ユーザーが見つかりません", { status: 404 });
  }
  
  return json({ user });
}

// 通常のコンポーネント
export default function UserProfile() {
  const { user } = useLoaderData();
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>メール: {user.email}</p>
    </div>
  );
}

// エラーが発生したときに表示されるコンポーネント
export function CatchBoundary() {
  const caught = useCatch();
  
  return (
    <div className="error-container">
      <h1>エラー {caught.status}</h1>
      <p>{caught.data}</p>
    </div>
  );
}

// 予期しないエラーが発生したときに表示されるコンポーネント
export function ErrorBoundary({ error }) {
  return (
    <div className="error-container">
      <h1>エラーが発生しました</h1>
      <p>{error.message}</p>
    </div>
  );
}

React Router v6.4以降でのエラーハンドリング

React Routerでも同様のエラーハンドリングが可能です:

import { useRouteError } from 'react-router-dom';

function ErrorPage() {
  const error = useRouteError();
  
  return (
    <div className="error-container">
      <h1>エラーが発生しました</h1>
      <p>
        {error.status === 404 
          ? "ページが見つかりません" 
          : error.message || "予期しないエラーが発生しました"}
      </p>
    </div>
  );
}

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: "users/:userId",
        element: <UserProfile />,
        loader: userLoader,
      },
    ],
  },
]);

フォーム送信

Remixでのフォーム送信

Remixでは、HTML標準の<form>要素を拡張した<Form>コンポーネントを提供しています。これにより、JavaScriptが無効な環境でも動作するフォームを簡単に実装できます:

// app/routes/login.jsx
import { json, redirect } from '@remix-run/node';
import { Form, useActionData, useTransition } from '@remix-run/react';

// フォーム送信時に実行されるaction関数
export async function action({ request }) {
  const formData = await request.formData();
  const email = formData.get('email');
  const password = formData.get('password');
  
  const errors = {};
  
  if (!email) errors.email = "メールアドレスは必須です";
  if (!password) errors.password = "パスワードは必須です";
  
  if (Object.keys(errors).length > 0) {
    return json({ errors }, { status: 400 });
  }
  
  try {
    const user = await login({ email, password });
    return redirect('/dashboard');
  } catch (error) {
    return json({ errors: { form: "ログインに失敗しました" } }, { status: 401 });
  }
}

export default function Login() {
  const actionData = useActionData();
  const transition = useTransition();
  const isSubmitting = transition.state === "submitting";
  
  return (
    <div>
      <h1>ログイン</h1>
      
      <Form method="post">
        <div>
          <label htmlFor="email">メールアドレス</label>
          <input 
            type="email" 
            id="email" 
            name="email" 
            required 
          />
          {actionData?.errors?.email && (
            <p className="error">{actionData.errors.email}</p>
          )}
        </div>
        
        <div>
          <label htmlFor="password">パスワード</label>
          <input 
            type="password" 
            id="password" 
            name="password" 
            required 
          />
          {actionData?.errors?.password && (
            <p className="error">{actionData.errors.password}</p>
          )}
        </div>
        
        {actionData?.errors?.form && (
          <p className="error">{actionData.errors.form}</p>
        )}
        
        <button type="submit" disabled={isSubmitting}>
          {isSubmitting ? "ログイン中..." : "ログイン"}
        </button>
      </Form>
    </div>
  );
}

React Router v6.4以降でのフォーム送信

React Router v6.4以降でも同様のフォーム処理が可能です:

import { Form, useActionData, useNavigation } from 'react-router-dom';

// action関数
async function loginAction({ request }) {
  const formData = await request.formData();
  const email = formData.get('email');
  const password = formData.get('password');
  
  // バリデーションと認証ロジック
  // ...
  
  return redirect('/dashboard');
}

function LoginPage() {
  const actionData = useActionData();
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";
  
  return (
    <div>
      <h1>ログイン</h1>
      
      <Form method="post">
        {/* フォームの内容 */}
        <button type="submit" disabled={isSubmitting}>
          {isSubmitting ? "ログイン中..." : "ログイン"}
        </button>
      </Form>
    </div>
  );
}

// ルート設定
const router = createBrowserRouter([
  {
    path: "login",
    element: <LoginPage />,
    action: loginAction
  }
]);

非同期フォーム送信とプログレッシブエンハンスメント

Remixの大きな特徴は、プログレッシブエンハンスメントをサポートしていることです。つまり、JavaScriptが無効な環境でも基本的な機能が動作します:

// app/routes/contact.jsx
import { redirect, json } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';

export async function action({ request }) {
  const formData = await request.formData();
  const name = formData.get('name');
  const message = formData.get('message');
  
  // サーバーサイドでのバリデーション
  const errors = {};
  if (!name) errors.name = "名前は必須です";
  if (!message) errors.message = "メッセージは必須です";
  
  if (Object.keys(errors).length > 0) {
    return json({ errors }, { status: 400 });
  }
  
  // メッセージを保存
  await saveMessage({ name, message });
  
  // 成功ページにリダイレクト
  return redirect('/contact/success');
}

export default function Contact() {
  const actionData = useActionData();
  
  return (
    <div>
      <h1>お問い合わせ</h1>
      
      <Form method="post">
        <div>
          <label htmlFor="name">お名前</label>
          <input type="text" id="name" name="name" />
          {actionData?.errors?.name && (
            <p className="error">{actionData.errors.name}</p>
          )}
        </div>
        
        <div>
          <label htmlFor="message">メッセージ</label>
          <textarea id="message" name="message" />
          {actionData?.errors?.message && (
            <p className="error">{actionData.errors.message}</p>
          )}
        </div>
        
        <button type="submit">送信</button>
      </Form>
    </div>
  );
}

このフォームは、JavaScriptが無効でも通常のHTMLフォームとして機能します。JavaScriptが有効な場合は、ページ全体の再読み込みなしでフォームを送信することができます。

まとめ

React RouterとRemixを活用することで、モダンで効率的な画面遷移を実現するWebアプリケーションを構築できます。特に、以下のポイントが重要です:

  1. 適切なルーティング設計: URL構造とUIの階層を適切に設計し、ユーザーにとって意味のあるナビゲーションを提供する

  2. データローディングの最適化: ローダー機能を活用して、必要なデータを効率的にロードし、ユーザー体験を向上させる

  3. エラーハンドリングの充実: 予期せぬエラーに対しても適切に対応し、ユーザーに分かりやすいフィードバックを提供する

  4. フォーム処理の改善: JavaScriptが無効な環境でも動作するフォームを実装し、アクセシビリティを向上させる

  5. SEO対策: サーバーサイドレンダリングを活用して、検索エンジンに適切にコンテンツを提供する

これらの機能を組み合わせることで、ユーザーフレンドリーでSEO対策も万全なWebアプリケーションを構築することができます。React RouterとRemixは、それぞれ異なる特性を持っていますが、どちらも効率的なルーティングとデータ処理を実現するための優れたツールです。

あなたのプロジェクトの要件に合わせて適切な選択をし、モダンなWeb開発を楽しんでください!

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

おすすめ記事

おすすめコンテンツ