Skip to content

React Router v6でのルート変更の検知方法

問題点

React Router v5では、useHistoryフックを使用してルート変更を監視することができました:

js
const history = useHistory();
history.listen((location, action) => {
  // ルート変更時の処理
});

しかし、v6ではuseHistoryuseNavigateに置き換えられ、navigateオブジェクトにはlistenメソッドが存在しないため、同じ方法は使えません:

js
// v6では機能しない
const navigate = useNavigate();
navigate.listen(...); // TypeError: listen is not a function

解決策

方法1: useLocationフックを使用する(推奨)

最もシンプルで公式に推奨される方法は、useLocationフックを使用することです:

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

function MyComponent() {
  const location = useLocation();
  
  useEffect(() => {
    // ルートが変更されるたびに実行される
    console.log('Route changed:', location.pathname);
    // ここでGoogle Analyticsなどのトラッキング処理を実行
    ga('send', 'pageview');
  }, [location]); // locationが変更されるたびに効果が発動
  
  return <div>コンテンツ</div>;
}

TIP

この方法は、コンポーネントがアンマウントされない限り有効です。ナビゲーションによってコンポーネントがアンマウントされる場合、useEffectは実行されません。

方法2: ナビゲーションタイプの検出

ブラウザの戻る/進むボタンなど、特定のナビゲーションアクションを検出したい場合は、useNavigationTypeフックを併用します:

js
import { useEffect } from 'react';
import { useLocation, useNavigationType } from 'react-router-dom';

export const useBackListener = (callback) => {
  const location = useLocation();
  const navType = useNavigationType();
  
  useEffect(() => {
    if (navType === 'POP' && location.key !== 'default') {
      // 戻る/進むボタンが押された時の処理
      callback();
    }
  }, [location, navType, callback]);
};

// 使用例
function MyComponent() {
  useBackListener(() => {
    console.log('戻るボタンが押されました');
  });
  
  return <div>コンテンツ</div>;
}

方法3: カスタムHistoryオブジェクトの使用(上級者向け)

高度なユースケースでは、カスタムhistoryオブジェクトを作成することも可能です:

js
// まずhistoryパッケージをインストール
// npm install history@5

// myHistory.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
js
// CustomRouter.js
import { useState, useLayoutEffect } from 'react';
import { Router } from 'react-router-dom';

export const CustomRouter = ({ history, ...props }) => {
  const [state, setState] = useState({
    action: history.action,
    location: history.location
  });

  useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      {...props}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
};
js
// App.js
import { CustomRouter } from './CustomRouter';
import history from './myHistory';

function App() {
  return (
    <CustomRouter history={history}>
      {/* ルート設定 */}
    </CustomRouter>
  );
}
js
// コンポーネント内での使用
import { useEffect } from 'react';
import history from './myHistory';

function MyComponent() {
  useEffect(() => {
    const unlisten = history.listen(({ location, action }) => {
      console.log('Route changed:', location.pathname);
    });
    
    return unlisten; // クリーンアップ関数
  }, []);
  
  return <div>コンテンツ</div>;
}

注意

この方法は複雑であり、React Routerの将来のバージョンで動作しなくなる可能性があります。公式のuseLocationを使用する方法を優先してください。

方法4: unstable_HistoryRouterの使用(試験的)

v6.4以降では、試験的なunstable_HistoryRouterコンポーネントが提供されています:

js
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
import history from './myHistory';

function App() {
  return (
    <HistoryRouter history={history}>
      {/* ルート設定 */}
    </HistoryRouter>
  );
}

警告

unstableという接頭辞が示す通り、このAPIは不安定であり、将来のバージョンで変更または削除される可能性があります。プロダクションコードでの使用は慎重に検討してください。

まとめ

  • 基本的なルート変更検知にはuseLocation + useEffectを使用する
  • ナビゲーションタイプの識別が必要な場合はuseNavigationTypeを併用する
  • 高度な制御が必要な場合のみカスタムhistoryオブジェクトを検討する
  • 試験的APIは将来の互換性に注意して使用する

React Router v6では、シンプルなuseLocationアプローチがほとんどのユースケースをカバーし、コードの可読性と保守性も向上します。