React Router v6 - Programmatic Navigation
Problem Statement
When upgrading to React Router v6, many developers encounter issues with programmatic navigation. The common scenario is that this.props.history.push()
no longer works as it did in previous versions. Instead, you might see the URL change in the browser's address bar, but the component doesn't actually render the new route.
This occurs because React Router v6 introduced significant changes to its API, including the removal of the history
object from props in favor of the new useNavigate
hook.
Solutions
1. Using the useNavigate Hook (Recommended)
The primary solution for programmatic navigation in React Router v6 is the useNavigate
hook. This hook returns a function that you can use to navigate programmatically.
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
// Navigate to a specific path
navigate('/users');
// Navigate with state
navigate('/users', { state: { user: userData } });
// Navigate backwards/forwards in history
navigate(-1); // Go back one page
navigate(1); // Go forward one page
navigate(-2); // Go back two pages
};
return (
<button onClick={handleClick}>
Go to Users
</button>
);
}
INFO
The useNavigate
hook can only be used inside functional components. If you're working with class components, see the alternative solutions below.
2. Accessing Navigation in Class Components
For class components, you have several options:
Option A: Create a Higher-Order Component (HOC)
// withRouter.js
import React from 'react';
import { useNavigate } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const navigate = useNavigate();
return <WrappedComponent {...props} navigate={navigate} />;
};
export default withRouter;
// YourClassComponent.js
import React, { Component } from 'react';
import withRouter from './withRouter';
class YourClassComponent extends Component {
handleSubmit = () => {
// Use navigate from props
this.props.navigate('/target-route');
};
render() {
return (
// Your component JSX
);
}
}
export default withRouter(YourClassComponent);
Option B: Wrap Class Component with Functional Component
import React from 'react';
import { useNavigate } from 'react-router-dom';
import YourClassComponent from './YourClassComponent';
export default function(props) {
const navigate = useNavigate();
return <YourClassComponent {...props} navigate={navigate} />;
}
3. Global Navigation with Custom Router
If you need to access navigation outside of React components, you can create a custom router:
// CustomRouter.js
import React from 'react';
import { Router } from 'react-router-dom';
import { BrowserHistory } from 'history';
const CustomRouter = ({ basename, children, history }) => {
const [state, setState] = React.useState({
action: history.action,
location: history.location,
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
export default CustomRouter;
// App.js
import { createBrowserHistory } from 'history';
import CustomRouter from './CustomRouter';
const history = createBrowserHistory();
function App() {
return (
<CustomRouter history={history}>
{/* Your app components */}
</CustomRouter>
);
}
// Now you can use history.push() anywhere
history.push('/target-route');
4. Navigation Setup for External Functions
If you need to navigate from non-React code (like API callbacks):
// navigation.js
import { useNavigate } from 'react-router-dom';
let navigator;
export const useNavigationSetup = () => {
navigator = useNavigate();
};
export const navigate = (to) => {
if (navigator) {
navigator(to);
}
};
// App.js
import { useNavigationSetup } from './navigation';
function App() {
useNavigationSetup();
// Your app components
}
// api.js
import { navigate } from './navigation';
export const loginUser = async (credentials) => {
try {
const response = await axios.post('/login', credentials);
navigate('/dashboard');
return response;
} catch (error) {
// Handle error
}
};
Common Pitfalls and Solutions
WARNING
Don't create multiple history instances. In your original code, you were creating a new createBrowserHistory()
instance inside your handler, which isn't connected to your router.
Incorrect approach:
// This won't work properly
handleSubmit() {
const history = createBrowserHistory(); // Creates isolated history
history.push('/users'); // Not connected to your router
}
Correct approach:
// Use the navigate function from useNavigate
handleSubmit() {
navigate('/users'); // Using the hook-provided navigate function
}
Migration from v5 to v6
If you're migrating from React Router v5, here's the main change:
v5 code:
import { useHistory } from 'react-router-dom';
function App() {
let history = useHistory();
function handleClick() {
history.push('/home');
}
return (
<button onClick={handleClick}>Go home</button>
);
}
v6 code:
import { useNavigate } from 'react-router-dom';
function App() {
let navigate = useNavigate();
function handleClick() {
navigate('/home');
}
return (
<button onClick={handleClick}>Go home</button>
);
}
Conclusion
React Router v6 simplifies navigation with the useNavigate
hook, which replaces the previous useHistory
approach. For most use cases, especially in functional components, useNavigate
is the recommended solution. For class components or external navigation needs, use the higher-order component pattern or a custom router setup.
Remember that the key difference in v6 is that navigation is now primarily handled through the useNavigate
hook rather than direct manipulation of the history object.