How can I programmatically navigate using React Router?
With React Router, I can use the Link element to create links which are natively handled by React Router. I see internally it calls this.context.transitionTo(...).
I want to do navigation. Not from a link, but from a dropdown selection (as an example). How can I do this in code? What is this.context?
I saw the Navigation mixin, but can I do this without mixins?
Programmatic navigation in React Router v6 is achieved using the useNavigate hook for functional components or the useHistory hook in older versions. You can call the returned function with the destination path or location object to trigger navigation from any component, including dropdown selections or other interactive elements.
Contents
- Getting Started with Programmatic Navigation
- Using the
useNavigateHook - Navigation from Class Components
- Advanced Navigation Options
- Handling Navigation State
- Migration from v5 to v6
- Best Practices
Getting Started with Programmatic Navigation
Programmatic navigation in React Router allows you to control navigation from within your application’s logic rather than relying solely on declarative <Link> components. This is particularly useful for scenarios where navigation should be triggered based on user interactions like form submissions, dropdown selections, or after asynchronous operations complete.
In modern React Router v6, the primary approach involves using the useNavigate hook, which provides a function that can be called with either a string path or a location object to trigger navigation. This approach replaces the older withRouter higher-order component and the Navigation mixin mentioned in your question.
The this.context you mentioned refers to React’s context API, which was previously used by React Router to provide routing information to components. However, in modern React applications, hooks have become the preferred approach for accessing context and other React features.
Using the useNavigate Hook
The useNavigate hook is the standard way to handle programmatic navigation in React Router v6. Here’s how to implement it:
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate();
const handleDropdownChange = (event) => {
const selectedValue = event.target.value;
navigate(selectedValue);
};
return (
<select onChange={handleDropdownChange}>
<option value="/home">Home</option>
<option value="/about">About</option>
<option value="/contact">Contact</option>
</select>
);
}
Navigation with Options
You can also pass options to the navigate function for more control:
const navigate = useNavigate();
// Navigate with replace option (doesn't add to history)
navigate('/dashboard', { replace: true });
// Navigate with state
navigate('/profile', {
state: { from: '/dashboard' },
replace: false
});
Relative Navigation
For relative navigation, you can use the relative option:
// Navigate one level up
navigate('..', { relative: 'path' });
// Navigate within the same route but with different params
navigate('..', { relative: 'path' });
Navigation from Class Components
While functional components with hooks are the modern approach, you may still need to handle navigation in class components. Here are several methods:
Using React Router Context
You can access the router context directly in class components:
import { useLocation, useNavigate } from 'react-router-dom';
class MyComponent extends React.Component {
static contextType = RouterContext;
handleNavigation = () => {
this.context.navigate('/new-path');
};
render() {
return <button onClick={this.handleNavigation}>Navigate</button>;
}
}
Higher-Order Component Pattern
Create a higher-order component to inject navigation:
import { useNavigate } from 'react-router-dom';
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
}
return ComponentWithRouterProp;
}
// Usage in class component
class MyComponent extends React.Component {
handleClick = () => {
this.props.navigate('/target');
};
render() {
return <button onClick={this.handleClick}>Navigate</button>;
}
}
export default withRouter(MyComponent);
Advanced Navigation Options
Navigation with Location Objects
Instead of simple strings, you can pass complete location objects:
const location = {
pathname: '/users',
search: '?sort=asc',
hash: '#section1',
state: { from: '/dashboard' }
};
navigate(location);
Programmatic Navigation with Confirmation
You can add confirmation dialogs before navigation:
const handleNavigate = () => {
if (window.confirm('Are you sure you want to leave?')) {
navigate('/new-page');
}
};
Navigation Based on Conditions
const navigate = useNavigate();
const handleSubmit = async () => {
const data = await submitForm();
if (data.success) {
navigate('/success');
} else {
navigate('/error');
}
};
Handling Navigation State
When navigating programmatically, you often want to pass state to the destination component:
// Source component
const navigate = useNavigate();
const handleClick = () => {
navigate('/profile', {
state: {
userId: '123',
timestamp: Date.now(),
referrer: '/dashboard'
}
});
};
// Destination component
import { useLocation } from 'react-router-dom';
function ProfilePage() {
const location = useLocation();
const { state } = location;
// Access passed state
const userId = state?.userId;
const referrer = state?.referrer;
return <div>Profile for user {userId}</div>;
}
Navigation State Persistence
Note that navigation state is not persisted across browser refreshes. For data that needs to persist, consider using URL parameters or a state management solution.
Migration from v5 to v6
If you’re migrating from React Router v5, here are the key changes:
v5 to v6 Migration Guide
v5 History Hook:
// React Router v5
import { useHistory } from 'react-router-dom';
function Component() {
const history = useHistory();
history.push('/new-path');
}
v6 Navigate Hook:
// React Router v6
import { useNavigate } from 'react-router-dom';
function Component() {
const navigate = useNavigate();
navigate('/new-path');
}
Key Differences
- Hook Names:
useHistory→useNavigate - Method Names:
history.push()→navigate() - Options:
history.push(path, state)→navigate(path, { state }) - Relative Navigation: New
relativeoption in v6
Common Migration Patterns
Replacing withRouter:
// v5 - withRouter HOC
export default withRouter(MyComponent);
// v6 - Custom HOC
import { useNavigate } from 'react-router-dom';
function withNavigation(Component) {
return function WrappedComponent(props) {
const navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
};
}
Best Practices
1. Prefer Hooks Over Context
Modern React Router encourages using hooks over the older context approach:
// Recommended: Using hooks
const navigate = useNavigate();
// Not recommended: Direct context access
const context = useContext(RouterContext);
2. Handle Navigation Loading States
Provide feedback during navigation:
function SubmitForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const navigate = useNavigate();
const handleSubmit = async () => {
setIsSubmitting(true);
try {
await submitData();
navigate('/success');
} catch (error) {
navigate('/error');
} finally {
setIsSubmitting(false);
}
};
return (
<button
onClick={handleSubmit}
disabled={isSubmitting}
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
);
}
3. Use TypeScript for Type Safety
When using TypeScript, you can get better type safety:
import { useNavigate, NavigateFunction } from 'react-router-dom';
function Component() {
const navigate: NavigateFunction = useNavigate();
// TypeScript will help catch invalid paths
navigate('/valid-path'); // ✅
navigate('/invalid-path'); // ❌ Will show type error
}
4. Avoid Hardcoded Paths
Use constants or configuration for navigation paths:
const ROUTES = {
HOME: '/',
ABOUT: '/about',
DASHBOARD: '/dashboard',
PROFILE: '/profile',
};
function Component() {
const navigate = useNavigate();
const navigateToDashboard = () => {
navigate(ROUTES.DASHBOARD);
};
}
5. Handle Navigation Errors
Implement error handling for navigation:
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
function Component() {
const navigate = useNavigate();
const safeNavigate = (path: string) => {
try {
navigate(path);
} catch (error) {
toast.error('Navigation failed');
console.error('Navigation error:', error);
}
};
}
Conclusion
Programmatic navigation in React Router v6 is straightforward using the useNavigate hook, which provides a clean, modern approach to controlling navigation from within your application logic. The hook returns a function that can be called with either string paths or location objects, giving you full control over how and when navigation occurs.
Key takeaways include:
- Use
useNavigate()for programmatic navigation in functional components - Pass location objects for complex navigation scenarios with parameters and state
- Create higher-order components for class components that need access to navigation
- Handle navigation state carefully, remembering it’s not persisted across refreshes
- Follow modern React patterns and use TypeScript for better type safety
The older approaches using context directly or mixins like the Navigation mixin are no longer recommended in favor of the hook-based approach, which aligns better with modern React development practices and provides better performance and type safety.