Migrating from history.push()
to useNavigate()
in React Router v6
React Router v6 introduced significant changes, replacing the history
API with the useNavigate()
hook. Here’s how to properly handle navigation in v6.
The Problem: history.push()
in v6
// ❌ Old approach (React Router v5)
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
const handleClick = () => {
history.push('/new-route'); // Doesn't work in v6
};
}
Why This Changed
- Simplified API:
useNavigate()
consolidates navigation methods - Hook-based: Aligns with React’s hooks ecosystem
- Type Safety: Better TypeScript support
- Relative Navigation: Improved handling of relative paths
Correct v6 Implementation
1. Basic Navigation
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/new-route'); // ✅ The new way
};
return <button onClick={handleClick}>Go</button>;
}
2. Equivalent Methods
v5 (history ) | v6 (useNavigate() ) |
---|---|
push() | navigate() |
replace() | navigate(path, { replace: true }) |
goBack() | navigate(-1) |
goForward() | navigate(1) |
go(n) | navigate(n) |
3. State Passing
// Navigating with state
navigate('/user', {
state: { userId: 123 },
replace: true // Optional replace flag
});
Special Cases
1. Outside Components
For navigation outside React components (e.g., in Redux actions):
// Create a history instance (history v5 package)
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
// Router setup
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
import { history } from './history';
<HistoryRouter history={history}>
<App />
</HistoryRouter>
// Usage anywhere
history.push('/somewhere');
2. Class Components
For class components, either:
Option 1: Wrap with a function component
class MyComponent extends React.Component {
render() {
return <Wrapper {...this.props} />;
}
}
function Wrapper(props) {
const navigate = useNavigate();
return <MyComponent {...props} navigate={navigate} />;
}
Option 2: Use the navigate
prop from React Router v6
<Route path="*" element={<MyComponent />} />
// MyComponent will receive navigate prop
Migration Tips
- Update Imports:
- Replace
useHistory
withuseNavigate
- Replace
history.push
withnavigate
- Search and Replace:
history.push(
→navigate(
history.replace(
→navigate(..., { replace: true })
- Relative Paths:
// v5: history.push(`${match.url}/new`)
// v6: navigate('new') // Simpler relative navigation
Common Pitfalls
- Forgetting to Remove
history
Dependency - Not Updating Tests:
// Old test mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({ push: jest.fn() }),
}));
// New test mock
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => jest.fn(),
}));
- Missing Replace Flag:
// Old: history.replace('/login')
// New: navigate('/login', { replace: true })
Best Practices
- Use
useNavigate
for all new code - Centralize Navigation Logic in custom hooks
- Type the Hook in TypeScript:
const navigate = useNavigate();
navigate('/path' as const); // For literal type safety
- Handle Errors:
try {
navigate('/protected');
} catch (error) {
console.error('Navigation failed:', error);
}
Remember: While you can still use the history
package directly in v6, useNavigate()
is the recommended approach for most use cases as it’s better integrated with React Router v6’s features.