useNavigate() が Router コンポーネントのコンテキスト内でのみ使用可能なエラー
問題説明
React Router v6 で useNavigate()
フックを使用する際に、以下のエラーが発生することがあります:
useNavigate() may be used only in the context of a <Router> component
このエラーは、useNavigate()
フックが <Router>
コンポーネント(<BrowserRouter>
, <HashRouter>
, <MemoryRouter>
など)のコンテキスト外で使用された場合に発生します。
根本原因
useNavigate()
フックは内部で React の Context API を使用しており、ルーティング関連の情報を提供する <Router>
コンポーネントにラップされている必要があります。
React Router のソースコードでは、useInRouterContext()
関数が現在のコンポーネントが <Router>
の子孫であるかどうかをチェックし、そうでない場合にこのエラーをスローします。
解決方法
方法1: アプリケーション全体を Router でラップする
最も一般的な解決策は、アプリケーションのルートコンポーネントを <BrowserRouter>
でラップすることです。
// index.js または main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// App.js
import { useNavigate } from 'react-router-dom';
function App() {
const navigate = useNavigate();
return (
<div>
<button onClick={() => navigate(-1)}>戻る</button>
{/* 他のコンポーネント */}
</div>
);
}
export default App;
方法2: 階層構造を適切に整理する
すべてのルーティング関連コンポーネントが <Router>
の子孫になるように構成します。
function RootComponent() {
const navigate = useNavigate();
return (
<div>
<button onClick={() => navigate(-1)}>戻る</button>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/home" element={<Home />} />
<Route path="/upcoming/:user" element={<Upcoming />} />
<Route path="/record/:user" element={<Record />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}
function App() {
return (
<BrowserRouter>
<RootComponent />
</BrowserRouter>
);
}
テスト環境での対処法
テスト時の注意点
単体テストで useNavigate()
を使用するコンポーネントをテストする場合、明示的に <Router>
でラップする必要があります。
MemoryRouter を使用する方法
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import MyComponent from './MyComponent';
test('コンポーネントのテスト', () => {
render(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
);
expect(screen.getByText('テキスト')).toBeInTheDocument();
});
ルート付きのテスト
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import MyComponent from './MyComponent';
test('ルート付きコンポーネントのテスト', () => {
render(
<MemoryRouter initialEntries={['/path']}>
<Routes>
<Route path="/path" element={<MyComponent />} />
</Routes>
</MemoryRouter>
);
expect(screen.getByText('テキスト')).toBeInTheDocument();
});
よくある間違いと解決策
インポート元の不一致
バージョン不一致
異なるバージョンの React Router からインポートすると、このエラーが発生することがあります。すべてのインポートを react-router-dom
から行うように統一してください。
// 正しいインポート
import { useNavigate, BrowserRouter, Routes, Route } from 'react-router-dom';
// 間違ったインポート(react-router と react-router-dom を混在させる)
import { useNavigate } from 'react-router'; // ❌
import { BrowserRouter } from 'react-router-dom'; // ❌
コンポーネント階層の問題
すべての useNavigate()
を使用するコンポーネントは、<Router>
コンポーネントの子孫である必要があります。
// 間違った例
<BrowserRouter>
<AuthProvider> {/* useNavigate() を使用 */}
<NavBar />
<Routes>
<Route path="/login" element={<LoginForm />} />
</Routes>
</AuthProvider>
</BrowserRouter>
<OtherComponent /> {/* ここで useNavigate() を使用するとエラー */}
// 正しい例
<BrowserRouter>
<AuthProvider> {/* useNavigate() を使用 */}
<NavBar />
<Routes>
<Route path="/login" element={<LoginForm />} />
</Routes>
<OtherComponent /> {/* Router の内側なので useNavigate() 使用可能 */}
</AuthProvider>
</BrowserRouter>
まとめ
useNavigate() may be used only in the context of a <Router> component
エラーは、React Router のコンテキスト外でナビゲーションフックを使用しようとした際に発生します。この問題を解決するには:
- アプリケーション全体を
<BrowserRouter>
でラップする - テスト時は
<MemoryRouter>
を使用する - すべてのインポートを
react-router-dom
から行うように統一する - コンポーネント階層を確認し、すべてのルーティング関連コンポーネントが
<Router>
の子孫になるようにする
これらの対策を行うことで、React Router のナビゲーション機能を正しく使用できるようになります。