No articles found
Try different keywords or browse our categories
Fix: React Router DOM useHistory is not exported - Complete Migration Guide
Learn how to fix the 'useHistory is not exported' error in React Router DOM. This guide covers React Router v6 changes, migration steps, and best practices for navigation hooks.
The ‘useHistory is not exported’ error is a common issue that React developers encounter when upgrading to React Router v6 or when there are version mismatches in their React Router DOM installation. This error occurs because useHistory was replaced with useNavigate in React Router v6 as part of a major API redesign.
This comprehensive guide explains what changed in React Router v6, why this error occurs, and provides multiple solutions to fix it in your React applications with clean code examples and directory structure.
What Changed in React Router v6?
React Router v6 introduced significant API changes to make navigation more intuitive and consistent. The most notable change is that useHistory was replaced with useNavigate, and the navigation API was simplified to provide better type safety and developer experience.
Key Changes:
- useHistory → useNavigate: New navigation hook with different API
- push/replace → navigate(): Simplified navigation function
- useParams/useLocation: Still available but with some changes
- Route Configuration: New way to define routes using
createBrowserRouter
Understanding the Problem
In React Router v5, navigation was handled using the history object:
// React Router v5 (Old)
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
const handleClick = () => {
history.push('/new-path');
};
return <button onClick={handleClick}>Go to new page</button>;
}
But in React Router v6, this approach no longer works:
// ❌ This will cause an error in React Router v6
import { useHistory } from 'react-router-dom'; // ❌ useHistory is not exported
Typical React Router Project Structure:
my-react-app/
├── package.json
├── src/
│ ├── App.jsx
│ ├── index.js
│ ├── components/
│ │ ├── Navigation.jsx
│ │ └── ProtectedRoute.jsx
│ ├── pages/
│ │ ├── Home.jsx
│ │ └── Profile.jsx
│ └── routes/
│ └── Router.jsx
Solution 1: Update to useNavigate (Recommended)
The primary solution is to replace useHistory with useNavigate from React Router v6.
❌ Before (React Router v5):
// components/Navigation.jsx
import { useHistory } from 'react-router-dom';
function Navigation() {
const history = useHistory();
const goToProfile = () => {
history.push('/profile');
};
const goBack = () => {
history.goBack();
};
const replacePage = () => {
history.replace('/new-page');
};
return (
<div>
<button onClick={goToProfile}>Go to Profile</button>
<button onClick={goBack}>Go Back</button>
<button onClick={replacePage}>Replace Page</button>
</div>
);
}
export default Navigation;
✅ After (React Router v6):
// components/Navigation.jsx
import { useNavigate } from 'react-router-dom';
function Navigation() {
const navigate = useNavigate();
const goToProfile = () => {
navigate('/profile'); // ✅ Simple navigation
};
const goBack = () => {
navigate(-1); // ✅ Go back
};
const replacePage = () => {
navigate('/new-page', { replace: true }); // ✅ Replace instead of push
};
const goToHome = () => {
navigate('/', { replace: true });
};
return (
<div>
<button onClick={goToProfile}>Go to Profile</button>
<button onClick={goBack}>Go Back</button>
<button onClick={replacePage}>Replace Page</button>
<button onClick={goToHome}>Go Home</button>
</div>
);
}
export default Navigation;
Solution 2: Update Package Dependencies
Ensure you have the correct React Router v6 dependencies installed.
Before (React Router v5):
{
"dependencies": {
"react-router-dom": "^5.3.0"
}
}
After (React Router v6):
{
"dependencies": {
"react-router-dom": "^6.8.0"
}
}
Update Command:
npm install react-router-dom@latest
Solution 3: Update Route Configuration
React Router v6 uses a different approach for defining routes.
❌ Before (React Router v5):
// App.jsx
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './pages/Home';
import Profile from './pages/Profile';
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/profile" component={Profile} />
</Switch>
</Router>
);
}
export default App;
✅ After (React Router v6):
// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Profile from './pages/Profile';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Solution 4: Using createBrowserRouter (Advanced)
For more complex routing, use the new createBrowserRouter.
routes/router.js:
// routes/router.js
import { createBrowserRouter } from 'react-router-dom';
import App from '../App';
import Home from '../pages/Home';
import Profile from '../pages/Profile';
export const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{
index: true,
element: <Home />
},
{
path: '/profile',
element: <Profile />
}
]
}
]);
App.jsx:
// App.jsx
import { RouterProvider } from 'react-router-dom';
import { router } from './routes/router';
function App() {
return <RouterProvider router={router} />;
}
export default App;
Solution 5: Handling Navigation Parameters
The navigation API has changed for passing parameters.
❌ Before (React Router v5):
// components/Navigation.jsx
import { useHistory } from 'react-router-dom';
function Navigation() {
const history = useHistory();
const goToProfile = (userId) => {
history.push(`/profile/${userId}`);
};
const goToProfileWithState = (data) => {
history.push('/profile', { data });
};
return <div>Navigation component</div>;
}
✅ After (React Router v6):
// components/Navigation.jsx
import { useNavigate } from 'react-router-dom';
function Navigation() {
const navigate = useNavigate();
const goToProfile = (userId) => {
navigate(`/profile/${userId}`); // ✅ Navigate with params
};
const goToProfileWithState = (data) => {
navigate('/profile', { state: { data } }); // ✅ Pass state
};
const goToProfileWithSearchParams = (search) => {
navigate(`/profile?search=${search}`); // ✅ With search params
};
return <div>Navigation component</div>;
}
Solution 6: Handling Programmatic Navigation
For navigation outside of components, use useNavigate with custom hooks.
hooks/useNavigation.js:
// hooks/useNavigation.js
import { useNavigate } from 'react-router-dom';
export function useNavigation() {
const navigate = useNavigate();
const navigateTo = (path, options = {}) => {
navigate(path, options);
};
const navigateBack = () => {
navigate(-1);
};
const navigateForward = () => {
navigate(1);
};
return {
navigateTo,
navigateBack,
navigateForward
};
}
// Usage in component
function MyComponent() {
const { navigateTo, navigateBack } = useNavigation();
return (
<div>
<button onClick={() => navigateTo('/profile')}>Go to Profile</button>
<button onClick={navigateBack}>Go Back</button>
</div>
);
}
Solution 7: Handling Redirects
Redirects work differently in React Router v6.
❌ Before (React Router v5):
// components/ProtectedRoute.jsx
import { Redirect } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = checkAuth();
return isAuthenticated ? children : <Redirect to="/login" />;
}
✅ After (React Router v6):
// components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = checkAuth();
return isAuthenticated ? children : <Navigate to="/login" replace />;
}
Solution 8: Handling Nested Routes
Nested routes have a different syntax in v6.
❌ Before (React Router v5):
// components/Dashboard.jsx
import { Route, Switch, useRouteMatch } from 'react-router-dom';
import Profile from './Profile';
import Settings from './Settings';
function Dashboard() {
const { path } = useRouteMatch();
return (
<div>
<Switch>
<Route exact path={path} component={Profile} />
<Route path={`${path}/settings`} component={Settings} />
</Switch>
</div>
);
}
✅ After (React Router v6):
// components/Dashboard.jsx
import { Outlet, useMatch } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<nav>
<Link to="profile">Profile</Link>
<Link to="settings">Settings</Link>
</nav>
<Outlet /> {/* ✅ Renders child routes */}
</div>
);
}
// In App.jsx
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
Complete Project Structure After Migration
my-react-app/
├── package.json
├── package-lock.json
├── node_modules/
├── src/
│ ├── App.jsx
│ ├── index.js
│ ├── components/
│ │ ├── Navigation.jsx
│ │ └── ProtectedRoute.jsx
│ ├── pages/
│ │ ├── Home.jsx
│ │ └── Profile.jsx
│ ├── routes/
│ │ └── router.js
│ └── hooks/
│ └── useNavigation.js
Working Code Examples
Complete Navigation Component:
// components/Navigation.jsx
import { useNavigate, useLocation } from 'react-router-dom';
import { useState } from 'react';
function Navigation() {
const navigate = useNavigate();
const location = useLocation();
const [searchTerm, setSearchTerm] = useState('');
const handleSearch = () => {
if (searchTerm) {
navigate(`/search?q=${encodeURIComponent(searchTerm)}`);
}
};
const goToProfile = (userId) => {
navigate(`/profile/${userId}`);
};
const goToProfileWithState = (userData) => {
navigate('/profile', {
state: {
user: userData,
from: location.pathname
}
});
};
const goBack = () => {
navigate(-1);
};
const goHome = () => {
navigate('/', { replace: true });
};
return (
<nav style={{ padding: '1rem' }}>
<div style={{ marginBottom: '1rem' }}>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
<button onClick={handleSearch}>Search</button>
</div>
<div>
<button onClick={goHome}>Home</button>
<button onClick={() => goToProfile(123)}>Profile</button>
<button onClick={() => goToProfileWithState({ id: 1, name: 'John' })}>
Profile with State
</button>
<button onClick={goBack}>Back</button>
</div>
</nav>
);
}
export default Navigation;
Complete App Setup:
// App.jsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Navigation from './components/Navigation';
import Home from './pages/Home';
import Profile from './pages/Profile';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<BrowserRouter>
<div className="App">
<Navigation />
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
<Route path="/profile/:id" element={<Profile />} />
<Route path="/search" element={<SearchResults />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
Best Practices for React Router v6
1. Use Element Prop Instead of Component
// ✅ Correct in v6
<Route path="/profile" element={<Profile />} />
// ❌ Wrong in v6
<Route path="/profile" component={Profile} />
2. Leverage Outlet for Nested Routes
// ✅ Use Outlet for nested routes
function Layout() {
return (
<div>
<Header />
<Outlet /> {/* ✅ Renders child routes */}
<Footer />
</div>
);
}
3. Use Navigate for Redirects
// ✅ Use Navigate component
<Navigate to="/login" replace />
// ❌ Don't use Redirect
<Redirect to="/login" />
4. Handle Navigation Parameters Properly
// ✅ Navigate with state
navigate('/profile', { state: { data: userData } });
// ✅ Navigate with search params
navigate(`/search?q=${query}`);
Debugging Steps
Step 1: Check React Router Version
npm list react-router-dom
Step 2: Verify Imports
// ❌ Wrong import
import { useHistory } from 'react-router-dom';
// ✅ Correct import
import { useNavigate } from 'react-router-dom';
Step 3: Update All Navigation Code
Search for all instances of useHistory and replace with useNavigate.
Step 4: Test Navigation
npm start
# Test all navigation functionality
Common Migration Issues and Solutions
Issue 1: Missing Dependencies
# Ensure React Router v6 is installed
npm install react-router-dom@latest
Issue 2: Route Configuration
// ❌ Old way
<Switch>
<Route path="/" component={Home} />
</Switch>
// ✅ New way
<Routes>
<Route path="/" element={<Home />} />
</Routes>
Issue 3: Programmatic Navigation
// ❌ Old way
history.push('/path');
// ✅ New way
navigate('/path');
Performance Considerations
1. Lazy Loading Routes
// Use lazy loading for better performance
import { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Routes>
<Route
path="/lazy"
element={
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
}
/>
</Routes>
);
}
2. Memoization
// Memoize navigation functions when needed
const memoizedNavigate = useCallback((path) => {
navigate(path);
}, [navigate]);
Security Considerations
1. Validate Navigation Paths
// ✅ Validate navigation paths
const safeNavigate = (path) => {
if (path.startsWith('/')) {
navigate(path);
}
};
2. Sanitize User Input
// ✅ Sanitize user input before navigation
const handleUserInput = (input) => {
const sanitized = sanitizePath(input);
navigate(`/search?q=${encodeURIComponent(sanitized)}`);
};
Testing Navigation
1. Unit Tests
// Using React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import Navigation from '../components/Navigation';
test('navigates to profile', () => {
render(
<MemoryRouter>
<Navigation />
</MemoryRouter>
);
fireEvent.click(screen.getByText('Profile'));
// Test navigation behavior
});
2. Integration Tests
// Test complete navigation flow
test('complete navigation flow', () => {
// Test route changes, state passing, etc.
});
Alternative Solutions
1. Downgrade to React Router v5 (Not Recommended)
npm install react-router-dom@5
2. Use a Compatibility Layer
// Create a compatibility hook
const useHistory = () => {
const navigate = useNavigate();
return {
push: (path) => navigate(path),
replace: (path) => navigate(path, { replace: true }),
goBack: () => navigate(-1)
};
};
Migration Checklist
- Update react-router-dom to v6
- Replace all useHistory imports with useNavigate
- Update route configuration from Switch to Routes
- Change component prop to element prop in routes
- Update Redirect components to Navigate components
- Test all navigation functionality
- Update nested route configurations
- Update documentation for team members
Conclusion
The ‘useHistory is not exported’ error occurs when using React Router v5 syntax with React Router v6. By updating to use useNavigate and following the new API patterns introduced in React Router v6, you can resolve this error and take advantage of the improved navigation system.
The key changes include replacing useHistory with useNavigate, updating route configuration from Switch to Routes, and using the element prop instead of component. With these updates, your React applications will be fully compatible with React Router v6 and provide a better developer experience.
Remember to test thoroughly after migration and update all navigation-related code to use the new API patterns for optimal performance and functionality.
Related Articles
Fix: Invalid React hook call. Hooks can only be called inside of the body of a function component
Learn how to fix the 'Invalid hook call' error in React. This guide covers all causes, solutions, and best practices for proper React hook usage with step-by-step examples.
Fix: Module not found: Can't resolve 'react/jsx-runtime' - Complete Solution Guide
Learn how to fix the 'Module not found: Can't resolve react/jsx-runtime' error in React projects. This guide covers causes, solutions, and prevention strategies with step-by-step instructions.
Fix: npm ERR! ERESOLVE unable to resolve dependency tree in React Projects
Learn how to fix the 'npm ERR! ERESOLVE unable to resolve dependency tree' error in React projects. This guide covers causes, solutions, and best practices for dependency management.