React Router 05 ⚛️ Navigation Links — Using Link components
The previous chapter covered the fallback route for unmatched URLs. This chapter covers the mechanism that users actually click to move between routes: the <Link>, <NavLink>, and <Navigate> components. A plain HTML <a href="..."> triggers a full page reload. The browser sends a new HTTP request, the server returns a new HTML document, and the React application re-initializes. The router’s client-side navigation is bypassed entirely. The <Link> component is the replacement: it renders an <a> tag with a valid href (for accessibility and middle-click) but intercepts the click event, prevents the default browser navigation, and calls the router’s history.push (or history.replace) instead. The route change happens in memory, the <RouterProvider> re-renders the matched component, and the URL in the address bar is updated. No page reload occurs.
Key point: The <Link to="/path"> component is the client-side navigation mechanism. The to prop accepts a string path, an object with pathname, search, and hash, or a function. The <NavLink> is a variant that adds an active class when the route matches. The <Navigate> component is the programmatic redirect, rendered in the JSX tree. The useNavigate hook is the imperative version, called in an event handler or an effect.
Why Link components require a specific mental model
The full-reload problem. An <a href="/about"> navigates to /about via the browser’s default behavior. The browser fetches the document from the server, and the React application mounts from scratch. The state is lost, the router’s history is reset, and the transition is not instant. The <Link> component prevents this: it calls event.preventDefault() and delegates the navigation to the router. The router updates the history state and re-renders the matched component. The URL changes, but the document is not reloaded.
The accessibility problem. The <Link> renders an <a> tag with a real href attribute. This is not cosmetic. Screen readers announce the element as a link. The keyboard navigation (Tab, Enter) works. The right-click “open in new tab” works. The browser’s status bar shows the URL on hover. A component that renders a <div> with an onClick handler loses all of these behaviors. The <Link> is the accessible navigation element.
The to prop problem. The to prop accepts a string (to="/about"), an object (to={{ pathname: "/users", search: "?sort=name" }}), or a function (to={(location) => ...}). The string form is the most common. The object form is useful for constructing the URL with the search parameters or the hash. The function form is for relative navigation based on the current location.
The active state problem. The <NavLink> is the variant of <Link> that adds a class to the rendered element when the current route matches the to prop. The default class is active, and the className prop can be a function that receives the isActive boolean. The <NavLink> is the correct choice for the navigation menu, where the current section is highlighted.
The programmatic navigation problem. The <Navigate> component is the declarative redirect. Rendered in the JSX tree, it triggers a navigation to the to prop when the component mounts. The useNavigate hook is the imperative version, called in an event handler or an effect. The <Navigate> is for the auth guards and the redirects after a form submission. The useNavigate is for the button clicks and the timeout redirects.
The trade-off. The <Link> is the correct choice for the user-initiated navigation. The <Navigate> and useNavigate are for the programmatic navigation. The <a> is never the correct choice in a React Router application, except for external URLs that leave the application.
a. The <Link> component and the to prop
The <Link> is imported from react-router. The to prop is the destination.
import { Link } from "react-router";
function NavBar() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users">Users</Link>
</nav>
);
}
The rendered HTML is an <a href="/about"> element. The browser sees a valid link. The click handler is attached by the router. The keyboard and the right-click work as expected.
The to prop accepts an object for constructing the URL.
<Link
to={{
pathname: "/users",
search: "?sort=name",
hash: "#top",
}}
>
Sorted users
</Link>
The to prop accepts a function that receives the current location.
<Link to={(location) => ({ ...location, pathname: "/about" })}>
About (relative)
</Link>
The function form is for the relative navigation based on the current location. The string form with a relative path (to="about") is relative to the current route’s path, not the current URL. The two forms are different and the function form is more explicit.
b. The <NavLink> component and the active state
The <NavLink> is the variant of <Link> that adds the active class. The default class is active.
import { NavLink } from "react-router";
function NavBar() {
return (
<nav>
<NavLink to="/" end>Home</NavLink>
<NavLink to="/about">About</NavLink>
<NavLink to="/users">Users</NavLink>
</nav>
);
}
The end prop restricts the match to the exact path. Without it, the <NavLink to="/"> is active for every URL that starts with /, which is every URL. The end prop is required for the root link.
The className prop can be a function that receives the isActive and isPending booleans.
<NavLink
to="/about"
className={({ isActive, isPending }) =>
isActive ? "nav-active" : isPending ? "nav-pending" : "nav"
}
>
About
</NavLink>
The isActive is true when the current route matches the to prop. The isPending is true during the navigation transition (Data mode with loaders). The function form is the mechanism for the custom class names.
The style prop can also be a function, which is useful for the inline styles.
<NavLink
to="/about"
style={({ isActive }) => ({ fontWeight: isActive ? "bold" : "normal" })}
>
About
</NavLink>
c. The <Navigate> component and the useNavigate hook
The <Navigate> component is the declarative redirect. It navigates to the to prop when it mounts.
import { Navigate } from "react-router";
function ProtectedRoute({ isAuthenticated, children }) {
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
The replace prop replaces the current history entry instead of pushing a new one. The user cannot press “back” to return to the protected route. This is the correct behavior for the auth redirect.
The useNavigate hook is the imperative version. It returns a function that navigates to the given path.
import { useNavigate } from "react-router";
function LoginForm() {
const navigate = useNavigate();
async function handleSubmit(event) {
event.preventDefault();
const success = await login(event.target);
if (success) {
navigate("/dashboard", { replace: true });
}
}
return <form onSubmit={handleSubmit}>...</form>;
}
The navigate(-1) navigates back one entry in the history. The navigate(1) navigates forward. The navigate("/path", { replace: true }) replaces the current entry. The navigate("/path", { state: { ... } }) passes the state that the useLocation hook can read.
navigate("/path", { state: { from: "login" } });
The useNavigate is the correct choice in the event handlers. The <Navigate> is the correct choice in the render output, when the navigation is conditional on the component’s state.
Complete Example Session
# ============================================
# PART 1: BASIC LINK
# ============================================
import { Link } from "react-router";
<Link to="/about">About</Link>
# ============================================
# PART 2: LINK WITH OBJECT
# ============================================
<Link to={{ pathname: "/users", search: "?sort=name" }}>
Users
</Link>
# ============================================
# PART 3: NAVLINK
# ============================================
import { NavLink } from "react-router";
<NavLink to="/about">About</NavLink>
# ============================================
# PART 4: NAVLINK WITH END
# ============================================
<NavLink to="/" end>Home</NavLink>
# ============================================
# PART 5: NAVLINK WITH CLASS FUNCTION
# ============================================
<NavLink
to="/about"
className={({ isActive }) => isActive ? "active" : ""}
>
About
</NavLink>
# ============================================
# PART 6: NAVIGATE COMPONENT
# ============================================
import { Navigate } from "react-router";
if (!isAuthenticated) return <Navigate to="/login" replace />;
# ============================================
# PART 7: USE NAVIGATE HOOK
# ============================================
import { useNavigate } from "react-router";
const navigate = useNavigate();
navigate("/dashboard", { replace: true });
# ============================================
# PART 8: NAVIGATE WITH STATE
# ============================================
navigate("/path", { state: { from: "login" } });
# ============================================
# PART 9: VERIFY NAVIGATION
# ============================================
# Click "About" → URL changes to /about → No page reload.
# Use NavLink → "About" has active class when on /about.
# Use Navigate → Redirect happens when component mounts.
The nine parts trace the navigation: basic link, object link, NavLink, NavLink with end, NavLink with class function, Navigate component, useNavigate hook, navigate with state, and verification.
Quick Reference
Components
| Component | Purpose |
|---|---|
<Link> | Client-side navigation |
<NavLink> | Navigation with active state |
<Navigate> | Declarative redirect |
Hooks
| Hook | Returns |
|---|---|
useNavigate | Navigation function |
useLocation | Current location |
useParams | URL parameters |
to Prop Forms
| Form | Example |
|---|---|
| String | to="/about" |
| Object | to={{ pathname, search, hash }} |
| Function | to={(loc) => loc.pathname + "/extra"} |
useNavigate Options
| Option | Effect |
|---|---|
replace: true | Replace history entry |
state: {} | Pass state to location |
-1, 1 | Back, forward |
NavLink Props
| Prop | Purpose |
|---|---|
to | Destination |
end | Exact match |
className | String or function |
style | Object or function |
Best Practices
✅ Do This:
<Link to="/about">About</Link>
<!-- Use Link for user navigation --> # ✅
<NavLink to="/" end>Home</NavLink>
<!-- Use end for the root link --> # ✅
navigate("/path", { replace: true });
<!-- Use replace for auth redirects --> # ✅
❌ Don’t Do This:
<a href="/about">About</a>
<!-- Full page reload --> # ❌
<NavLink to="/">Home</NavLink>
<!-- Active for every URL; missing end --> # ❌
navigate("/path", { replace: false });
<!-- Breaks the back button after auth redirect --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Full page reload | Used <a> instead of <Link> | Use <Link> |
| Home link always active | No end prop on <NavLink> | Add end |
| Back button returns to login | replace: false after auth | replace: true |
| Active class not applied | className not a function | Use function form |
to object ignored | Wrong prop form | Use object or string |
Real-World Examples
1. Basic Link
<Link to="/about">About</Link>
2. Link with Search
<Link to={{ pathname: "/users", search: "?sort=name" }}>Users</Link>
3. NavLink
<NavLink to="/about">About</NavLink>
4. NavLink with End
<NavLink to="/" end>Home</NavLink>
5. NavLink with Class
<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>About</NavLink>
6. Navigate Component
<Navigate to="/login" replace />
7. useNavigate
const navigate = useNavigate();
navigate("/dashboard");
8. Navigate with State
navigate("/path", { state: { from: "login" } });
9. Navigate Back
navigate(-1);
10. Read Location State
const location = useLocation();
const { from } = location.state || {};
Visual
Link vs Anchor
┌──────────────────────────────────────────────┐
│ <a href="/about"> │
│ ├── Full page reload │
│ ├── State lost │
│ └── React re-mounts │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ <Link to="/about"> │
│ ├── Client-side navigation │
│ ├── State preserved │
│ └── Component re-renders │
└──────────────────────────────────────────────┘
Navigation Types
┌──────────────────────────────────────────────┐
│ User-initiated │
│ → <Link>, <NavLink> │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Declarative redirect │
│ → <Navigate> │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Imperative navigation │
│ → useNavigate() │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Client-side link | <Link to="/path"> |
| Active state link | <NavLink> |
| Exact match | end prop |
| Declarative redirect | <Navigate> |
| Imperative navigation | useNavigate() |
| History replace | { replace: true } |
| Pass state | { state: { ... } } |
| Back navigation | navigate(-1) |
Key takeaways:
- The
<Link>prevents the full page reload. It renders an<a>with a validhreffor accessibility but intercepts the click event and delegates to the router. The route change happens in memory, and the component re-renders without re-initializing the application. - The
<NavLink>adds the active state. TheclassNameprop can be a function that receives theisActiveboolean. Theendprop restricts the match to the exact path, which is required for the root link. - The
<Navigate>is the declarative redirect. It is rendered in the JSX tree and triggers the navigation when it mounts. Thereplaceprop is the correct choice for the auth redirects, which prevents the back button from returning to the protected route. - The
useNavigatehook is the imperative version. It is called in the event handler or in the effect. Thenavigate(-1)navigates back, and thenavigate("/path", { state })passes the state that theuseLocationhook can read. - The
<a>tag is for external URLs only. Any internal navigation must use<Link>or<NavLink>to preserve the SPA behavior.
Remember: The <Link> is the client-side navigation. The <NavLink> is the active state. The <Navigate> is the redirect. The useNavigate is the imperative version. The replace: true is the history replacement. The end is the exact match. The exam tests the distinction and the active state configuration.
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!