Using history.push()
in React Router v6
In React Router v6, the traditional history.push()
approach from v4/v5 has been replaced with the useNavigate()
hook. Attempting to use the old history API directly will cause errors.
The Problem
// ❌ Wrong - Old v5 approach in v6
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
function App() {
const handleClick = () => {
history.push('/dashboard'); // Doesn't work in v6
};
return <button onClick={handleClick}>Go to Dashboard</button>;
}
Why this fails in v6:
- React Router v6 removed direct history access
- Navigation is now handled through the
useNavigate
hook - The history object isn’t automatically synchronized with the router
- Results in broken navigation state
Correct Solutions
1. Basic useNavigate
Usage (Recommended)
import { useNavigate } from 'react-router-dom';
function App() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/dashboard'); // ✅ Correct v6 approach
};
return <button onClick={handleClick}>Go to Dashboard</button>;
}
2. Replace Navigation (Like replaceState
)
function App() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/dashboard', { replace: true }); // ✅ Replaces current entry
};
return <button onClick={handleClick}>Login</button>;
}
3. With State
navigate('/user', {
state: { id: 123 }, // ✅ Pass state
replace: false // Default
});
4. Relative Navigation
navigate('../parent-route'); // ✅ Relative paths
navigate(-1); // ✅ Go back (like history.goBack())
navigate(2); // ✅ Go forward 2 entries
Migration from v5 to v6
Old v5 Pattern
// React Router v5
import { useHistory } from 'react-router-dom';
function OldComponent() {
const history = useHistory();
history.push('/path');
history.replace('/path');
history.goBack();
}
New v6 Pattern
// React Router v6
import { useNavigate } from 'react-router-dom';
function NewComponent() {
const navigate = useNavigate();
navigate('/path'); // push
navigate('/path', { replace: true }); // replace
navigate(-1); // goBack
}
Advanced Patterns
1. Custom History Wrapper (For Special Cases)
import { createBrowserHistory } from 'history';
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
const history = createBrowserHistory();
// In your app setup
<HistoryRouter history={history}>
<App />
</HistoryRouter>
// Now can use both approaches
history.push('/old-way');
navigate('/new-way'); // Also works
2. Programmatic Navigation Outside Components
// navigation.js
let navigateRef;
export const setNavigate = (navigate) => {
navigateRef = navigate;
};
export const navigate = (to) => {
if (navigateRef) navigateRef(to);
};
// App.jsx
import { useNavigate } from 'react-router-dom';
import { setNavigate } from './navigation';
function App() {
const navigate = useNavigate();
setNavigate(navigate);
// ...
}
// Anywhere else
import { navigate } from './navigation';
navigate('/somewhere');
3. TypeScript Support
const navigate = useNavigate();
// Typed state
navigate('/user', {
state: { userId: 123 } // ✅ Type-safe state
});
// Receiving state
const { state } = useLocation();
const userId = state?.userId; // Type: number | undefined
Common Mistakes
- Using v5 code in v6:
import { useHistory } from 'react-router-dom'; // ❌ v5 only
- Forgetting the navigate function:
// ❌ Missing hook call
<button onClick={() => useNavigate()('/path')}>Click</button>
- Incorrect relative paths:
navigate('./path'); // ❌ Might not work as expected
Best Practices
- Use
useNavigate
for all navigation in v6 - Keep navigation centralized in custom hooks/services
- Use the options object for replace/state
- Prefer absolute paths for main routes
- Test navigation with
MemoryRouter
in tests