React Router 10 ⚛️ Imperative Nav — Redirection with useNavigate
The previous chapter covered the <Link> and <NavLink> components, which are the declarative navigation mechanisms. This chapter covers the imperative navigation: the useNavigate hook, which returns a function that programmatically navigates to a route. The LFCS exam does not test React Router, but the front-end engineering certification paths do. The critical distinction is between the declarative <Navigate> component and the imperative useNavigate hook. The <Navigate> is rendered in the JSX tree and triggers the navigation when the component mounts. The useNavigate is called in an event handler, an effect, or a callback. The <Navigate> is for the render-time redirects (the auth guard). The useNavigate is for the action-time redirects (the form submission, the button click, the timeout). The hook must be called inside a component that is rendered within the <RouterProvider>, or it throws the “useNavigate may be used only in the context of a Router component” error.
Key point: The useNavigate hook returns a function. The function accepts a path (string), a number (the history delta), or an options object. The navigate("/path") pushes a new history entry. The navigate("/path", { replace: true }) replaces the current entry. The navigate(-1) goes back. The navigate("/path", { state: { ... } }) passes the state that the useLocation hook reads. The hook must be called at the top level of the component, not inside a condition or a loop.
Why imperative navigation requires a specific mental model
The hook rules problem. The useNavigate is a React hook. The hooks must be called at the top level of the component or another hook. The if (condition) { navigate("/path") } inside the component body is not a hook call in a condition—the navigate call is fine. But the if (condition) { const navigate = useNavigate() } is a violation of the rules of hooks, because the hook call is conditional. The correct pattern is to call the hook at the top and use the returned function inside the condition.
The render-time versus action-time problem. The <Navigate> is the declarative component. It is rendered in the JSX, and the navigation happens when the component renders. The useNavigate is the imperative function. It is called in the event handler or in the effect, and the navigation happens when the function is called. The <Navigate> is the correct choice for the auth redirect that depends on the state. The useNavigate is the correct choice for the form submission that happens after the async operation.
The replace versus push problem. The navigate("/path") pushes a new entry. The user can press the back button to return to the previous page. The navigate("/path", { replace: true }) replaces the current entry. The user cannot return to the previous page. The replace is the correct choice for the login redirect (the user should not be able to press back to return to the protected route) and for the search filter (the filter should not pollute the history).
The state problem. The navigate("/path", { state: { from: "/login" } }) passes the state that the useLocation hook reads. The state is not the URL. The state is the in-memory object that is preserved in the history entry. The login page can read the state to redirect the user back to the original page after the login. The state is lost on the page refresh; the URL is preserved.
The redirect component problem. The redirect() function is the loader-level alternative. The loader throws a redirect, and the router handles it before the component renders. The redirect("/login") is the declarative redirect for the auth guard in the loader. The useNavigate is the imperative redirect in the component. The <Navigate> is the declarative redirect in the JSX. The three mechanisms are for the three levels: the loader, the component logic, and the render output.
The trade-off. The <Link> is for the user-initiated navigation. The useNavigate is for the programmatic navigation. The redirect is for the loader. The <Navigate> is for the render-time condition. The exam tests the distinction and the hook rules.
a. The basic useNavigate and the history delta
The useNavigate hook returns the navigate function. The function accepts a string, a number, or an options object.
import { useNavigate } from "react-router";
function LogoutButton() {
const navigate = useNavigate();
const handleLogout = async () => {
await logout();
navigate("/login");
};
return <button onClick={handleLogout}>Logout</button>;
}
The navigate("/login") pushes a new history entry. The user is on /login, and the back button returns to the previous page. The navigate("/login", { replace: true }) replaces the current entry, which is the correct choice for the logout.
The navigate(-1) goes back one entry. The navigate(1) goes forward. The navigate(-2) goes back two entries.
function BackButton() {
const navigate = useNavigate();
return <button onClick={() => navigate(-1)}>Back</button>;
}
The navigate(-1) is the equivalent of the browser’s back button. The hook is the programmatic access to the history stack.
b. The replace option and the state
The { replace: true } option replaces the current history entry. The { state } option passes the state object.
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = async (event) => {
event.preventDefault();
const success = await login(event.target);
if (success) {
navigate("/dashboard", { replace: true });
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
The replace: true ensures that the user cannot press the back button to return to the login page after the login. The dashboard is the new entry, and the previous entry is the page before the login.
The state is the way to pass the data to the next route without the URL parameter.
navigate("/checkout", { state: { cartId: "abc123" } });
The next route reads the state with the useLocation hook.
import { useLocation } from "react-router";
function Checkout() {
const location = useLocation();
const { cartId } = location.state || {};
return <p>Cart: {cartId}</p>;
}
The state is preserved in the history entry. The page refresh loses the state. The URL parameter is the alternative if the state must survive the refresh.
c. The redirect function and the <Navigate> component
The redirect() function is the loader-level redirect. The loader throws the redirect, and the router navigates before the component renders.
import { redirect } from "react-router";
export async function loader({ request }) {
const user = await getUser(request);
if (!user) {
throw redirect("/login");
}
return { user };
}
The loader runs before the component. The redirect is thrown, and the router navigates to the login page. The user never sees the protected component. The redirect is the correct choice for the auth guard that runs in the loader.
The <Navigate> component is the render-time redirect. It is rendered in the JSX, and the navigation happens when the component renders.
import { Navigate } from "react-router";
function ProtectedRoute({ isAuthenticated, children }) {
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
The <Navigate> is the declarative form. The replace prop is the equivalent of the { replace: true } option. The <Navigate> is the correct choice for the condition that depends on the component’s props or state.
The distinction is the level. The redirect is the loader (the data loading). The <Navigate> is the render (the JSX). The useNavigate is the action (the event handler). The three are complementary.
Complete Example Session
# ============================================
# PART 1: BASIC NAVIGATE
# ============================================
const navigate = useNavigate();
navigate("/dashboard");
# ============================================
# PART 2: REPLACE HISTORY
# ============================================
navigate("/dashboard", { replace: true });
# ============================================
# PART 3: GO BACK
# ============================================
navigate(-1);
# ============================================
# PART 4: PASS STATE
# ============================================
navigate("/checkout", { state: { cartId: "abc" } });
# ============================================
# PART 5: READ STATE
# ============================================
const location = useLocation();
const { cartId } = location.state || {};
# ============================================
# PART 6: LOGOUT EXAMPLE
# ============================================
const handleLogout = () => {
logout();
navigate("/login", { replace: true });
};
# ============================================
# PART 7: LOADER REDIRECT
# ============================================
export async function loader() {
const user = await getUser();
if (!user) throw redirect("/login");
return { user };
}
# ============================================
# PART 8: COMPONENT REDIRECT
# ============================================
if (!isAuthenticated) return <Navigate to="/login" replace />;
# ============================================
# PART 9: VERIFY
# ============================================
# Click logout → navigate to /login, no back
# Submit login → navigate to /dashboard, replace
# Loader without user → redirect to /login
The nine parts trace the imperative navigation: basic, replace, back, state, read state, logout, loader redirect, component redirect, and verification.
Quick Reference
useNavigate Forms
| Form | Effect |
|---|---|
navigate("/path") | Push new entry |
navigate("/path", { replace: true }) | Replace entry |
navigate(-1) | Back one entry |
navigate(1) | Forward one entry |
navigate("/path", { state: { ... } }) | Pass state |
Options
| Option | Effect |
|---|---|
replace | Replace history entry |
state | Pass state object |
preventScrollReset | Prevent scroll reset |
relative | Relative to route |
Redirect Mechanisms
| Mechanism | Level |
|---|---|
redirect() | Loader |
<Navigate> | Render (JSX) |
useNavigate | Action (event) |
Hooks
| Hook | Returns |
|---|---|
useNavigate | Navigate function |
useLocation | Current location |
useSearchParams | Search params |
Best Practices
✅ Do This:
const navigate = useNavigate();
// At the top of the component
- ✅ Call the hook at the top
navigate("/dashboard", { replace: true });
- ✅ Replace history after login
if (!isAuthenticated) throw redirect("/login");
- ✅ Use
redirectin the loader
❌ Don’t Do This:
if (condition) { const navigate = useNavigate(); }
<!-- Conditional hook call --> # ❌
navigate("/path");
# After login
<!-- Back button returns to login --> # ❌
<a href="/dashboard">Dashboard</a>
<!-- Full page reload --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Hook error | Conditional useNavigate | Call at top level |
| Back to login | Used push instead of replace | { replace: true } |
| State lost on refresh | State not URL | Use URL param for persistence |
| Redirect not triggered | Wrong level | Use redirect in loader |
| Component re-renders | Navigation on render | Use <Navigate> or effect |
Real-World Examples
1. Basic Navigate
navigate("/dashboard");
2. Replace
navigate("/dashboard", { replace: true });
3. Back
navigate(-1);
4. Forward
navigate(1);
5. State
navigate("/checkout", { state: { cartId } });
6. Read State
const { cartId } = useLocation().state || {};
7. Loader Redirect
throw redirect("/login");
8. Component Redirect
return <Navigate to="/login" replace />;
9. Logout
logout(); navigate("/login", { replace: true });
10. After Form
await save(); navigate("/list");
Visual
Navigation Levels
┌──────────────────────────────────────────────┐
│ Loader │
│ → throw redirect("/login") │
│ → Before the component renders │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Render │
│ → <Navigate to="/login" replace /> │
│ → When the component renders │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Action │
│ → navigate("/login") │
│ → In the event handler │
└──────────────────────────────────────────────┘
History Stack
┌──────────────────────────────────────────────┐
│ Push: navigate("/dashboard") │
│ Stack: [home, login, dashboard] │
│ Back → /login │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Replace: navigate("/dashboard", {replace}) │
│ Stack: [home, dashboard] │
│ Back → /home │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Hook | useNavigate() |
| Push | navigate("/path") |
| Replace | navigate("/path", { replace: true }) |
| Back | navigate(-1) |
| State | navigate("/path", { state }) |
| Loader redirect | throw redirect(...) |
| Render redirect | <Navigate to="..." replace /> |
| Hook call | Top level only |
Key takeaways:
useNavigateis the imperative navigation. It is called in the event handler, the effect, or the callback. The<Navigate>is the declarative render-time redirect. Theredirect()is the loader-level redirect. The three are complementary.- The hook must be called at the top level. The conditional hook call is a rules-of-hooks violation. The
navigatefunction is used inside the condition; the hook is called outside. - The
{ replace: true }replaces the history entry. The user cannot press back to return to the previous page. The login redirect and the filter are the common cases. - The
{ state }passes the in-memory data. The state is preserved in the history entry, but it is lost on the page refresh. The URL parameter is the alternative for the data that must survive the refresh. - The navigation is a push by default. The back button returns to the previous page. The replace is the exception for the cases where the previous page is not a valid return target.
Remember: The useNavigate is the action. The <Navigate> is the render. The redirect is the loader. The push is the default. The replace is the history replacement. The state is the in-memory. The hook is the top-level. The exam tests the hook rules and the redirect distinction.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!