Programmatic Navigation
Link and NavLink handle navigation triggered by user clicks on anchors. Programmatic navigation handles the cases where you need to navigate in code — after a form submission, after a successful API call, after a logout, or when redirecting back to the intended destination after login.
useNavigate
useNavigate() returns a navigate function. Call it with a path string to push a new entry onto the browser's history stack:
import { useNavigate } from 'react-router-dom'
function SearchBar() {
const navigate = useNavigate()
const [query, setQuery] = React.useState('')
const handleSearch = (e: React.FormEvent) => {
e.preventDefault()
if (query.trim()) {
navigate(`/search?q=${encodeURIComponent(query)}`)
}
}
return (
<form onSubmit={handleSearch}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search…"
/>
<button type="submit">Search</button>
</form>
)
}Navigate After Form Submit
The most common use of useNavigate is redirecting after a successful form submission — creating a new post, completing checkout, or saving settings:
import { useNavigate } from 'react-router-dom'
function NewPostForm() {
const navigate = useNavigate()
const [title, setTitle] = React.useState('')
const [body, setBody] = React.useState('')
const [isSubmitting, setIsSubmitting] = React.useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSubmitting(true)
try {
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, body }),
})
if (!res.ok) throw new Error('Failed to create post')
const post = await res.json()
// Navigate to the new post's detail page
navigate(`/posts/${post.id}`)
} catch (err) {
console.error(err)
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit}>
<input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" />
<textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="Body" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Publishing…' : 'Publish Post'}
</button>
</form>
)
}Going Back with navigate(-1)
Pass a number to navigate to move through browser history. -1 goes back, 1 goes forward, -2 goes back two entries:
function BackButton() {
const navigate = useNavigate()
return (
<button onClick={() => navigate(-1)}>
← Back
</button>
)
}
// More specific: go back two steps
function CloseWizard() {
const navigate = useNavigate()
return <button onClick={() => navigate(-2)}>Cancel Wizard</button>
}replace: true — No History Entry
Pass { replace: true } as the second argument to replace the
current history entry instead of pushing a new one. The user cannot
navigate back to the replaced page:
// After login, replace the login page so the Back button doesn't return to it
navigate('/dashboard', { replace: true })
// After a successful password reset, replace the reset form
navigate('/login?success=true', { replace: true })
// Redirect from an old URL to a new one
function OldPostPage() {
const { id } = useParams()
return <Navigate to={`/blog/${id}`} replace />
}Passing State with navigate
The second argument also accepts a state property. State is passed in the location object and survives the navigation — use it to carry data that doesn't belong in the URL:
import { useNavigate, useLocation } from 'react-router-dom'
// Page A: navigate with state
function CheckoutPage() {
const navigate = useNavigate()
const location = useLocation()
const handlePurchase = async () => {
const order = await submitOrder()
navigate('/order-confirmation', {
state: {
orderId: order.id,
total: order.total,
from: location.pathname,
},
})
}
return <button onClick={handlePurchase}>Complete Purchase</button>
}
// Page B: read navigation state
function OrderConfirmation() {
const location = useLocation()
const { orderId, total } = location.state ?? {}
if (!orderId) {
// State is gone if the user refreshes — handle gracefully
return <p>No order data found. Check your email for confirmation.</p>
}
return (
<div>
<h1>Order Confirmed!</h1>
<p>Order #{orderId}</p>
<p>Total: ${total?.toFixed(2)}</p>
</div>
)
}useLocation — Reading the Current Location
useLocation returns the current location object with pathname, search, hash, and state. It re-renders your component whenever the location changes:
import { useLocation } from 'react-router-dom'
function PageTracker() {
const location = useLocation()
React.useEffect(() => {
// Track page views on every navigation
analytics.track('page_view', {
path: location.pathname,
search: location.search,
})
}, [location])
return null // renders nothing, just tracks
}
// Reading path parts:
function Breadcrumb() {
const location = useLocation()
const segments = location.pathname.split('/').filter(Boolean)
return (
<nav>
{segments.map((seg, i) => (
<span key={i}>{seg} / </span>
))}
</nav>
)
}Login Redirect — Navigate Back to Origin
The classic pattern: a user tries to visit a protected page, is redirected to login, and after a successful login is sent back to where they were trying to go. Store the intended destination in location state:
import { useNavigate, useLocation, Navigate } from 'react-router-dom'
// ProtectedRoute stores the attempted URL in state
function ProtectedRoute({ children }) {
const { isLoggedIn } = useAuth()
const location = useLocation()
if (!isLoggedIn) {
return (
<Navigate
to="/login"
state={{ from: location.pathname + location.search }}
replace
/>
)
}
return children
}
// Login page reads state.from and redirects there on success
function LoginPage() {
const navigate = useNavigate()
const location = useLocation()
const from = location.state?.from ?? '/dashboard'
const handleLogin = async (credentials) => {
await login(credentials)
// Go back to the page they were trying to reach
navigate(from, { replace: true })
}
return <LoginForm onSubmit={handleLogin} />
}Navigate Options Summary
navigate("/path")— push new history entry, user can press Back.navigate("/path", { replace: true })— replace current entry, no Back entry.navigate(-1)— go back one step in history.navigate(1)— go forward one step in history.navigate("/path", { state: { key: value } })— pass data to the destination page.useLocation().state— read state on the destination page.useLocation().pathname— current path, useful for analytics and breadcrumbs.