React Router: useHistoryからuseNavigateへの移行と使い方を徹底解説

React Routerは、Reactアプリケーションにおけるルーティング機能を提供するライブラリとして広く利用されています。その中で、以前のバージョンではuseHistoryというフックが使用されていました。しかし、React Router v6のリリースに伴い、useHistoryは廃止され、新たにuseNavigateが導入されました。この変更は、より直感的で柔軟なルーティングを可能にするための進化といえます。本記事では、useHistoryuseNavigateの違いを詳しく解説し、既存のコードを新しい仕組みに移行する方法を学ぶことで、最新のReact Routerを活用できるようになることを目指します。

目次

React Routerの進化とuseNavigateの導入背景


React Routerは、バージョンごとに進化を遂げ、より使いやすく効率的なルーティング機能を提供してきました。バージョン6では、コードの簡潔さと直感性を重視した変更が多く盛り込まれました。その中でも注目すべき変更が、useHistoryからuseNavigateへの移行です。

React Router v6の改善点


React Router v6では、以下のような改善が行われました:

  • ルート定義のシンプル化:ネストされたルート構造を簡単に記述可能。
  • フックの統一性:従来のフックより直感的で分かりやすいAPIに刷新。
  • パフォーマンスの向上:無駄なレンダリングを削減。

useNavigateが導入された理由


従来のuseHistoryは、履歴管理を直接操作する仕組みでしたが、APIが複雑になりやすく、初心者にとって分かりづらい側面がありました。一方、useNavigateは次のような特徴を持ちます:

  • 単純なAPI設計で初心者にも扱いやすい。
  • 履歴スタックの直接操作ではなく、アクションベースのナビゲーションが可能。
  • 新しいReact Routerの設計思想に沿った拡張性を提供。

この進化により、React Routerの学習曲線が下がり、ルーティングの設定や操作がより直感的になりました。

useHistoryとuseNavigateの基本的な違い

useHistoryとは


React Router v5まで使用されていたuseHistoryは、履歴スタックを直接操作するためのフックでした。主な特徴は以下の通りです:

  • history.push(path)を使って指定したパスへ移動。
  • history.replace(path)で現在の履歴を置き換え。
  • 履歴スタックにアクセスして戻る、進むといった操作が可能。

この方法は強力ですが、historyオブジェクトの詳細を把握する必要があり、コードが複雑になることがありました。

useNavigateとは


React Router v6で導入されたuseNavigateは、シンプルかつ直感的なナビゲーションを可能にします。主な特徴は以下の通りです:

  • navigate(path)を呼び出すだけで簡単に指定したパスへ移動。
  • 第2引数で状態やオプションを渡して柔軟な遷移が可能。
  • APIが簡潔で、ナビゲーションに特化している。

具体的な違いの比較

特徴useHistoryuseNavigate
ナビゲーション方法history.push(path)navigate(path)
履歴の置き換えhistory.replace(path)navigate(path, {replace: true})
状態の渡し方history.push(path, state)navigate(path, {state})
学習コストやや高い低い

useNavigateの採用により、コードがより簡潔になり、学習が容易になった点が大きな利点です。

useNavigateの基本的な使用例

基本的なナビゲーション


useNavigateを使用すると、指定したパスへ簡単に移動できます。以下の例では、ボタンをクリックしたときに別のページへ遷移します。

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

function HomePage() {
  const navigate = useNavigate();

  const goToAbout = () => {
    navigate('/about');
  };

  return (
    <div>
      <h1>Home Page</h1>
      <button onClick={goToAbout}>Go to About Page</button>
    </div>
  );
}

export default HomePage;

状態を渡しながら遷移


useNavigateの第2引数にstateを指定することで、遷移先にデータを渡すことができます。

const goToProfile = () => {
  navigate('/profile', { state: { userId: 123 } });
};

遷移先のコンポーネントではuseLocationを使って渡された状態を受け取ります。

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

function ProfilePage() {
  const location = useLocation();
  const { userId } = location.state || {};

  return (
    <div>
      <h1>Profile Page</h1>
      <p>User ID: {userId}</p>
    </div>
  );
}

export default ProfilePage;

履歴の置き換え


通常のナビゲーションは履歴に追加されますが、replace: trueを指定することで、履歴を置き換えることができます。

const redirectToLogin = () => {
  navigate('/login', { replace: true });
};

この例では、ログインページにリダイレクトした際に「戻る」操作で元のページに戻れなくなります。

まとめ


useNavigateは、React Router v6の直感的で簡単なナビゲーションを提供します。これを活用することで、複雑なルーティング操作もコードを短く保ちながら実現できます。

useNavigateの高度な使用例

動的パスでのナビゲーション


useNavigateを使うと、動的なパラメータを含むパスにも柔軟に対応できます。以下の例では、特定のIDを動的に指定して遷移します。

function ProductList() {
  const navigate = useNavigate();

  const viewProduct = (productId) => {
    navigate(`/products/${productId}`);
  };

  return (
    <div>
      <h1>Product List</h1>
      <button onClick={() => viewProduct(101)}>View Product 101</button>
      <button onClick={() => viewProduct(102)}>View Product 102</button>
    </div>
  );
}

遷移先では、URLパラメータを取得して詳細を表示します。

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

function ProductPage() {
  const { id } = useParams();

  return (
    <div>
      <h1>Product Page</h1>
      <p>Product ID: {id}</p>
    </div>
  );
}

export default ProductPage;

状態と遷移先での利用


遷移時に状態を渡し、それを遷移先で使うこともできます。以下の例では、ログイン成功時にユーザー情報を渡して次のページへ遷移します。

function LoginPage() {
  const navigate = useNavigate();

  const handleLogin = () => {
    const userData = { name: 'John Doe', role: 'admin' };
    navigate('/dashboard', { state: userData });
  };

  return (
    <div>
      <h1>Login Page</h1>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
}

遷移先のコンポーネントでは、受け取った状態を活用します。

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

function Dashboard() {
  const location = useLocation();
  const { name, role } = location.state || {};

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Welcome, {name}! Your role is {role}.</p>
    </div>
  );
}

リダイレクト処理の実装


特定の条件下で自動的に別のページにリダイレクトする場合も、useNavigateが有効です。以下の例では、ユーザーが未ログインの場合にログインページへリダイレクトします。

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

function ProtectedPage() {
  const navigate = useNavigate();
  const isAuthenticated = false; // 本来は認証ステータスを取得する

  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/login', { replace: true });
    }
  }, [isAuthenticated, navigate]);

  return <h1>Protected Content</h1>;
}

複数の引数を伴う遷移


状態オブジェクトを利用して複数の値を一度に渡すことも可能です。

navigate('/summary', { state: { cart: items, total: totalPrice } });

遷移先では、この情報を利用して購入内容を表示することができます。

まとめ


useNavigateは、動的パスやリダイレクト、状態の受け渡しといった高度なシナリオにおいても柔軟に対応できます。この機能を使いこなすことで、Reactアプリのルーティング設計が一層効率化され、ユーザー体験が向上します。

useNavigateへの移行方法

useHistoryからuseNavigateへの基本的な移行手順


React Router v6にアップグレードした際に必要な変更点は、useHistoryuseNavigateに置き換えることです。この移行は比較的簡単で、以下の手順で対応できます。

ステップ1: React Routerのバージョン確認


useNavigateはReact Router v6で導入されているため、以下のコマンドでバージョンを確認し、必要に応じてアップデートしてください。

npm install react-router-dom@latest

ステップ2: useHistoryをuseNavigateに置き換え


useHistoryを使用しているコードを以下のように書き換えます。

変更前 (React Router v5)

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

function Example() {
  const history = useHistory();

  const navigateToPage = () => {
    history.push('/new-page');
  };

  return <button onClick={navigateToPage}>Go to New Page</button>;
}

変更後 (React Router v6)

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

function Example() {
  const navigate = useNavigate();

  const navigateToPage = () => {
    navigate('/new-page');
  };

  return <button onClick={navigateToPage}>Go to New Page</button>;
}

パラメータの渡し方の変更


以前のhistory.pushでは第2引数として状態を渡していましたが、useNavigateではオプションとして状態を渡します。

変更前 (React Router v5)

history.push('/profile', { userId: 123 });

変更後 (React Router v6)

navigate('/profile', { state: { userId: 123 } });

履歴の置き換え


history.replaceで行っていた履歴の置き換えは、useNavigatereplaceオプションで実現できます。

変更前 (React Router v5)

history.replace('/login');

変更後 (React Router v6)

navigate('/login', { replace: true });

動的なパスのサポート


動的パスに対するナビゲーションもuseNavigateで簡単に対応できます。

変更前 (React Router v5)

history.push(`/products/${id}`);

変更後 (React Router v6)

navigate(`/products/${id}`);

ユニットテストでの変更対応


テストコードでのuseHistoryモックは、以下のようにuseNavigateに変更してください。

変更前

jest.mock('react-router-dom', () => ({
  useHistory: () => ({ push: jest.fn() }),
}));

変更後

jest.mock('react-router-dom', () => ({
  useNavigate: jest.fn(),
}));

まとめ


useHistoryからuseNavigateへの移行は、APIの違いを理解し、コードを置き換えるだけで対応可能です。useNavigateのシンプルな構造により、ルーティング操作が簡素化され、より可読性の高いコードを実現できます。

よくある移行時の課題とその解決策

課題1: `state`の受け渡しに関する問題


useHistoryからuseNavigateに移行する際、stateの受け渡しが正しく設定されていない場合、遷移先でエラーが発生することがあります。

原因


以前のhistory.pushでは、stateを第2引数として渡していましたが、useNavigateではオプションとして明示的に設定する必要があります。

解決方法


以下のように、stateをオプションオブジェクトとして渡します。

変更前 (React Router v5)

history.push('/details', { itemId: 42 });

変更後 (React Router v6)

navigate('/details', { state: { itemId: 42 } });

遷移先ではuseLocationを使用してstateを取得します。

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

function DetailsPage() {
  const location = useLocation();
  const { itemId } = location.state || {};

  return <div>Item ID: {itemId}</div>;
}

課題2: `history.replace`の誤用


履歴の置き換え機能を持つhistory.replaceuseNavigateに移行する際に、オプションreplaceを指定し忘れるケースがあります。

解決方法


履歴を置き換える場合は、replace: trueを必ず指定します。

変更前 (React Router v5)

history.replace('/login');

変更後 (React Router v6)

navigate('/login', { replace: true });

課題3: 動的パラメータの処理


動的パスのナビゲーションで、パラメータの組み込みミスが発生することがあります。

解決方法


URLテンプレートを利用する場合、以下のように動的パラメータを組み込みます。

変更前 (React Router v5)

history.push(`/products/${productId}`);

変更後 (React Router v6)

navigate(`/products/${productId}`);

課題4: コンポーネントのテストにおける問題


useNavigateを使用するコンポーネントをテストする際、jestでのモックが必要です。これを正しく設定しないとテストが失敗します。

解決方法


useNavigateを以下のようにモックします。

設定例

jest.mock('react-router-dom', () => ({
  useNavigate: jest.fn(),
}));

そして、テスト内で適切に呼び出しを確認します。

import { render, screen, fireEvent } from '@testing-library/react';
import Example from './Example';

test('navigates to the new page on button click', () => {
  const mockNavigate = jest.fn();
  jest.mock('react-router-dom', () => ({
    useNavigate: () => mockNavigate,
  }));

  render(<Example />);
  fireEvent.click(screen.getByText('Go to New Page'));
  expect(mockNavigate).toHaveBeenCalledWith('/new-page');
});

課題5: 既存のRouter設定との互換性


移行後に、古いルート定義が新しいuseNavigateと整合しない場合があります。

解決方法


React Router v6のルート設定に従ってルーティングを再構成してください。ネストされたルートやelementの利用を適切に更新します。


まとめ


useNavigateへの移行中には、状態の渡し方、履歴の置き換え、動的パラメータ処理、テストのモック設定などに注意が必要です。これらの課題に対処することで、React Router v6の機能を最大限に活用できます。

useNavigateを活用したReactプロジェクトの改善例

シナリオ1: 動的ナビゲーションによるページ遷移の効率化


useNavigateを使うことで、パラメータ付きの動的ルートを効率的に処理できます。以下は、商品の詳細ページへ動的に遷移する例です。

コード例
商品リストページで各商品の「詳細を見る」ボタンをクリックすると、該当商品の詳細ページに遷移します。

function ProductList({ products }) {
  const navigate = useNavigate();

  const goToDetails = (id) => {
    navigate(`/products/${id}`);
  };

  return (
    <div>
      <h1>Product List</h1>
      {products.map((product) => (
        <div key={product.id}>
          <p>{product.name}</p>
          <button onClick={() => goToDetails(product.id)}>詳細を見る</button>
        </div>
      ))}
    </div>
  );
}

この方法を採用することで、複数の商品詳細ページを統一的に管理でき、コードの再利用性が向上します。


シナリオ2: 条件付きリダイレクトの自動化


ユーザーの状態に応じたリダイレクト処理を簡潔に実装できます。以下は、未ログイン状態でアクセスした場合にログインページにリダイレクトする例です。

コード例

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

function Dashboard({ isAuthenticated }) {
  const navigate = useNavigate();

  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/login', { replace: true });
    }
  }, [isAuthenticated, navigate]);

  return isAuthenticated ? <h1>Welcome to Dashboard</h1> : null;
}

この実装により、未認証ユーザーが保護されたページにアクセスするのを防ぎます。


シナリオ3: 状態を伴うナビゲーションでUX向上


useNavigateを活用すると、状態を渡しながら遷移することでユーザー体験を向上させることが可能です。以下は、ログイン成功時にユーザー情報をダッシュボードページに渡す例です。

コード例

function LoginPage() {
  const navigate = useNavigate();

  const handleLogin = () => {
    const user = { name: 'John Doe', role: 'Admin' };
    navigate('/dashboard', { state: { user } });
  };

  return (
    <div>
      <h1>Login Page</h1>
      <button onClick={handleLogin}>Login</button>
    </div>
  );
}

function Dashboard() {
  const { state } = useLocation();
  const { user } = state || {};

  return (
    <div>
      <h1>Dashboard</h1>
      {user && <p>Welcome, {user.name}. Your role is {user.role}.</p>}
    </div>
  );
}

これにより、遷移先のコンポーネントでAPIリクエストを再度発行する必要がなくなり、パフォーマンスが向上します。


シナリオ4: モーダル遷移を簡単に管理


useNavigateを使うと、モーダルを経由した遷移も管理しやすくなります。

コード例
以下は、商品の詳細をモーダルで表示し、そこから遷移する例です。

function ProductModal({ product }) {
  const navigate = useNavigate();

  const closeModal = () => {
    navigate(-1); // 前のページに戻る
  };

  return (
    <div className="modal">
      <h2>{product.name}</h2>
      <button onClick={closeModal}>Close</button>
    </div>
  );
}

このアプローチでは、履歴スタックを管理しつつ、ナビゲーションフローに柔軟性を持たせることができます。


シナリオ5: 国際化対応の動的ルーティング


URLに言語コードを組み込むことで、国際化対応を簡単に実現できます。

コード例

const changeLanguage = (lang) => {
  navigate(`/${lang}/home`);
};

言語切り替えボタンを提供し、動的に言語対応ルートへ遷移します。


まとめ


useNavigateを活用することで、Reactアプリのナビゲーションがより柔軟かつ効率的に管理可能になります。これらの改善例を取り入れることで、ユーザー体験を向上させ、コードの保守性を高めることができます。

演習問題とサンプルコード

演習1: 動的パスを使用したページ遷移


課題: ユーザーリストページから、ユーザーIDを動的に渡して詳細ページに遷移する機能を実装してください。
要件:

  1. ボタンをクリックすると、各ユーザーの詳細ページに遷移する。
  2. 詳細ページでは、URLパラメータからユーザーIDを取得して表示する。

サンプルコード:
以下のコードを参考にしてください。

// UserList.js
import React from 'react';
import { useNavigate } from 'react-router-dom';

function UserList() {
  const navigate = useNavigate();
  const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];

  const goToDetails = (id) => {
    navigate(`/users/${id}`);
  };

  return (
    <div>
      <h1>User List</h1>
      {users.map((user) => (
        <div key={user.id}>
          <p>{user.name}</p>
          <button onClick={() => goToDetails(user.id)}>View Details</button>
        </div>
      ))}
    </div>
  );
}

export default UserList;
// UserDetails.js
import React from 'react';
import { useParams } from 'react-router-dom';

function UserDetails() {
  const { id } = useParams();

  return (
    <div>
      <h1>User Details</h1>
      <p>User ID: {id}</p>
    </div>
  );
}

export default UserDetails;

演習2: 状態を渡してナビゲーション


課題: 商品ページから、商品情報を状態として渡し、カートページで受け取って表示してください。
要件:

  1. 商品ページから「カートに追加」をクリックすると、商品情報をstateとして渡す。
  2. カートページで受け取った商品情報を表示する。

サンプルコード:

// ProductPage.js
import React from 'react';
import { useNavigate } from 'react-router-dom';

function ProductPage() {
  const navigate = useNavigate();
  const product = { id: 101, name: 'Laptop', price: 999 };

  const addToCart = () => {
    navigate('/cart', { state: { product } });
  };

  return (
    <div>
      <h1>Product Page</h1>
      <p>{product.name} - ${product.price}</p>
      <button onClick={addToCart}>Add to Cart</button>
    </div>
  );
}

export default ProductPage;
// CartPage.js
import React from 'react';
import { useLocation } from 'react-router-dom';

function CartPage() {
  const location = useLocation();
  const { product } = location.state || {};

  return (
    <div>
      <h1>Cart Page</h1>
      {product ? (
        <p>
          Product: {product.name}, Price: ${product.price}
        </p>
      ) : (
        <p>No items in the cart</p>
      )}
    </div>
  );
}

export default CartPage;

演習3: 条件付きリダイレクト


課題: ログインしていない場合、保護されたページからログインページにリダイレクトする機能を実装してください。
要件:

  1. ユーザーが未ログインの場合、保護されたページにアクセスするとログインページにリダイレクトされる。
  2. ログイン後は、保護されたページにアクセス可能になる。

サンプルコード:

// ProtectedPage.js
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

function ProtectedPage({ isAuthenticated }) {
  const navigate = useNavigate();

  useEffect(() => {
    if (!isAuthenticated) {
      navigate('/login', { replace: true });
    }
  }, [isAuthenticated, navigate]);

  return <h1>Protected Content</h1>;
}

export default ProtectedPage;

練習問題の解答確認


これらの演習を通じて、useNavigateを活用した柔軟なルーティングを学ぶことができます。必要に応じてコードを変更し、さらに多様なシナリオに対応できるスキルを磨いてください。

まとめ


本記事では、React Router v6で導入されたuseNavigateの特徴と、それを活用した実践的なシナリオについて解説しました。useHistoryからの移行方法を具体的に示し、動的ナビゲーション、状態の受け渡し、リダイレクトの自動化など、useNavigateを使ったさまざまな例を紹介しました。

useNavigateの採用により、ルーティングの記述がシンプルで直感的になり、保守性の高いコードが実現できます。これを活用することで、Reactアプリの開発効率とユーザー体験を大きく向上させることができます。React Routerの新機能を活用し、最新の開発環境に適応しましょう。

コメント

コメントする

目次