Difficulty: Intermediate
While Link handles navigation triggered by user clicks, many scenarios require programmatic navigation - redirecting after a form submission, navigating after an API call, or redirecting based on authentication status. React Router v6 provides two mechanisms for this: the useNavigate hook for imperative navigation in event handlers and effects, and the Navigate component for declarative redirects in JSX.
useNavigate returns a navigate function that you can call to change the URL programmatically. Call navigate('/dashboard') to go to the dashboard, navigate(-1) to go back (like the browser's back button), or navigate(1) to go forward. The function also accepts an options object: navigate('/login', { replace: true }) replaces the current entry in the history stack instead of pushing a new one, and navigate('/dashboard', { state: { from: '/login' } }) passes state data to the destination route.
The Navigate component is the declarative counterpart of the navigate function. It redirects immediately when rendered. This is useful in JSX where imperative code would be awkward - for example, inside a route definition to redirect from an old URL to a new one, or inside a conditional render to redirect when a user is not authenticated. <Navigate to="/login" replace /> renders nothing visible but immediately redirects.
The replace option is important for the user experience. By default, navigation pushes a new entry onto the history stack, so the back button returns to the previous page. With replace: true, the current entry is replaced, so the back button skips the replaced page. This is appropriate for redirects (you don't want the back button to return to the redirect source) and post-login redirects (you don't want the back button to return to the login page).
Navigation state (the state option) allows passing data between routes without putting it in the URL. This is useful for passing context like 'where the user came from' so you can redirect back after login, or passing temporary data like a success message. The destination reads this state using the useLocation hook: location.state. Navigation state is stored in the browser's session history and survives page refreshes within the session.
// Simulating useNavigate
function createNavigate() {
const history = ['/home'];
let currentIndex = 0;
return function navigate(to, options) {
if (typeof to === 'number') {
currentIndex += to;
console.log('Navigate back/forward to:', history[currentIndex]);
} else if (options && options.replace) {
history[currentIndex] = to;
console.log('Replace current with:', to);
} else {
currentIndex++;
history.splice(currentIndex, 0, to);
console.log('Push:', to);
}
console.log('History:', JSON.stringify(history.slice(0, currentIndex + 1)));
};
}
const navigate = createNavigate();
navigate('/dashboard');
navigate('/settings');
navigate(-1); // Go back
navigate('/login', { replace: true }); // Replace current
navigate(path) pushes a new entry. navigate(-1) goes back. navigate(path, { replace: true }) replaces the current entry. This matches the browser's History API behavior.
function LoginForm() {
const handleSubmit = (credentials) => {
console.log('Submitting login for:', credentials.email);
// Simulate API call
const success = credentials.email === 'admin@test.com';
if (success) {
console.log('Login successful');
console.log('Navigate to: /dashboard (replace: true)');
// In real code: navigate('/dashboard', { replace: true })
} else {
console.log('Login failed');
console.log('Stay on: /login');
}
};
handleSubmit({ email: 'admin@test.com', password: 'pass' });
console.log('---');
handleSubmit({ email: 'wrong@test.com', password: 'pass' });
return null;
}
LoginForm();
After a successful login, navigate with replace: true so the back button doesn't return to the login page. On failure, stay on the login page to let the user retry.
function NavigateComponent(props) {
const destination = props.to;
const replace = props.replace || false;
console.log('Redirect to: ' + destination + ' (replace: ' + replace + ')');
}
// Simulating conditional rendering with Navigate
function ProtectedContent({ isAuthenticated }) {
if (!isAuthenticated) {
console.log('Not authenticated!');
NavigateComponent({ to: '/login', replace: true });
return null;
}
console.log('Showing protected content');
return null;
}
// Old URL redirect
function OldRoute() {
console.log('Old route /old-page visited');
NavigateComponent({ to: '/new-page', replace: true });
}
ProtectedContent({ isAuthenticated: false });
console.log('---');
ProtectedContent({ isAuthenticated: true });
console.log('---');
OldRoute();
The Navigate component triggers a redirect when rendered. It's useful for conditional redirects in JSX (authentication checks) and permanent redirects (old URLs to new ones).
useNavigate, Navigate component, redirects, programmatic navigation, navigation state