Clicking a link is only one way users move through a React app β code often needs to redirect them too, like after a login or a form submission. This lesson covers the useNavigate hook, how it replaced the older useHistory API, and how to control the browser's history stack with replace and delta navigation.
1Programmatic Navigation
Clicking a <Link> is great for user-initiated navigation, but sometimes you need to redirect from inside your code instead β after a successful login, after a form submits, or after an API call completes. This is called programmatic navigation, and it's handled separately from the declarative <Link> component.
// Programmatic Navigation: Controlling the FlowCode-Driven Routing
Navigating without Links
2The useNavigate Hook
In React Router v6, programmatic navigation is handled by the useNavigate hook. You call it at the top level of your component, like const navigate = useNavigate(), and it returns a single function you can call anywhere in your component to trigger navigation.
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
const handleClick = () => {
navigate('/dashboard');
};useNavigate
Your routing steering wheel.
3Basic Navigation
To move to a new page, call navigate() and pass the destination path, such as navigate('/dashboard'). Just like with <Link>, a path starting with / is absolute, while omitting the leading slash makes the navigation relative to the current route.
// v5: history.push('/home')
// v6: navigate('/home')Calling Navigate
navigate('/path')
4Upgrading from useHistory
If you're following an older tutorial from React Router v5 or earlier, you may see a different hook called useHistory instead. React Router v6 intentionally replaced it with useNavigate to simplify the API β the two aren't interchangeable, so useHistory-based code needs to be updated to work with v6.
navigate('/login', { replace: true });Goodbye History
Hello Navigate.
5push vs navigate
The old v5 API required calling history.push('/path') to navigate. Since v6's useNavigate returns the navigation function directly instead of an object with methods, you no longer call .push() β you just call navigate('/path') directly.
navigate(-1); // Go backSimpler Syntax
Just call it.
6Redirects and the Back Button
Every navigation the browser performs gets added to a history stack, and clicking 'Back' pops the most recent entry off that stack. Sometimes you don't want a redirect to add to that stack at all β for example, you don't want a user leaving a 'processing' page to be able to hit 'Back' and land on it again.
/* useNavigate Module Completed */The Stack
How the back button works.
7Step-by-Step Breakdown
Programmatic Navigation. Links (<Link>) are great for user-initiated clicks. But sometimes you need to navigate via codeβlike redirecting a user after a successful login, or moving them to a 'Thank You' page after submitting a form. This is called Programmatic Navigation.
The useNavigate Hook. In React Router v6, programmatic navigation is handled entirely by the useNavigate hook. You call the hook at the top level of your component, and it returns a powerful navigate function.
Basic Navigation. To move to a new page, simply call the navigate function and pass in the destination string (the path). Just like with Links, passing a path starting with / makes it absolute, while omitting it makes it relative.
Which function should you call in React Router v6 to programmatically move the user to /success?
- βpush
- βnavigate
Upgrading from useHistory. Warning: If you are watching tutorials from 2021 or earlier, they will be using React Router v5. In v5, this hook was called useHistory. The v6 update intentionally replaced it with useNavigate to make the API simpler and less confusing.
push vs navigate. In the old useHistory API, you had to call history.push('/path') to navigate. Because v6 changed the hook to return a function directly, you no longer call .push(). You just call the navigate function directly.
If you want to move a user to the /login route, which is the correct syntax in React Router v6?
- βhistory.push('/login');
- βnavigate('/login');
Redirects and the Back Button. Think about how a browser works. Every time you navigate, the browser adds that URL to a stack (an array). When the user clicks the browser's native 'Back' button, it pops the top item off the stack. But sometimes, you want to redirect the user WITHOUT adding to that stack.
Replacing History Entries. If a user goes /checkout -> /processing -> /success, you don't want them hitting 'Back' from /success to go back to /processing! To prevent this, pass { replace: true } as the second argument to navigate. This replaces /processing with /success in the history stack.
Which option should you pass to navigate to ensure the user cannot use the browser's Back button to return to the current route?
- βpush
- βreplace
Delta Navigation (Going Back). Sometimes you don't know the exact URL the user came from, you just want to send them 'back' to whatever it was. Instead of a string, pass a negative number to navigate(). Passing -1 tells React Router to go back one step in the history stack.
Delta Navigation (Going Forward). Similarly, passing positive numbers moves you forward in the history stack (if the user had previously gone back). This is identical to the browser's native 'Forward' button. Passing 2 goes forward two steps.
Passing Hidden State. Just like with the <Link> component, you can use navigate to pass hidden, non-URL state to the destination route. You pass it in the same options object as replace.
Navigation in Event Handlers. The most common place to use navigate is inside an event handler. For example, if you have a complex search form, you can gather the form data on submit, construct a search string, and push the user to a search results route.
Mastery Achieved. Programmatic navigation mastered! You can now control your application's flow from anywhere in your code, manipulate the history stack, and pass data safely.
Level Up π
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Programmatic Redirects Should Move Focus to the New Page's Content
Since `navigate()` doesn't trigger a full page reload, focus stays wherever it was before the redirect β after navigating programmatically (e.g., post-login), move focus to the new page's main heading so screen reader and keyboard users land somewhere meaningful.
2Avoid Auto-Navigating Away From a Page Without Warning
If a timer or background event triggers `navigate()` automatically, like an inactivity redirect, give users advance warning and a way to cancel it β an unannounced navigation is disorienting, especially for screen reader users who won't see a visual countdown.
SEO Implications
- 1
Client-Side Redirects via navigate() Are Invisible to Crawlers That Don't Execute JavaScript
A `navigate()` call inside a `useEffect` or event handler only runs after the page's JavaScript executes, so search engines that don't fully render client-side navigation may index the pre-redirect URL instead of the destination β use real HTTP redirects for SEO-critical redirect logic.
- 2
Using replace Prevents Redirect Pages From Polluting Browsing History Signals
Intermediate pages like `/processing` or `/login-success` that exist only briefly during a flow shouldn't remain in the user's browsing history β passing `{ replace: true }` keeps the history stack, and the pages a user might revisit or share, reflecting real destinations.
Best Practices
Use { replace: true } for Post-Action Redirects
After actions like completing a login or finishing checkout, navigate with `{ replace: true }` so the intermediate processing page is removed from history β this prevents a confusing or even duplicate-submission-triggering 'Back' button experience.
Prefer navigate(-1) Over a Hardcoded Path for Generic 'Back' Buttons
A back button that always calls `navigate('/dashboard')` breaks if the user arrived from somewhere else; `navigate(-1)` returns them to whatever page they actually came from, mirroring the browser's native Back button.
Frequent Bugs
After completing a multi-step flow like checkout or onboarding, clicking the browser's Back button re-triggers an intermediate step or resubmits an action.
Each step called `navigate()` with the default push behavior, leaving every intermediate page in the history stack. Use `navigate(path, { replace: true })` for steps that shouldn't be revisitable.
Code copied from an older tutorial calls `history.push('/path')` and throws an error that `history` is not defined.
That code is written for React Router v5's `useHistory` hook, which no longer exists in v6. Replace it with `const navigate = useNavigate();` and call `navigate('/path')` directly instead of `history.push()`.
Real-World Examples
Redirecting After Login Without a History Trap
After a successful login, the app calls `navigate('/dashboard', { replace: true })` so the login form is removed from history β pressing Back from the dashboard returns the user to wherever they were before logging in, not back to the login form.
async function handleLogin(credentials) {
await api.login(credentials);
navigate('/dashboard', { replace: true });
}