Navigation in React Router is not limited to clicking links — many actions trigger navigation programmatically: creating a post redirects to the new post’s page, logging out navigates to the home page, deleting a post navigates back to the list. The useNavigate hook returns a function for imperative navigation — calling it with a path changes the URL exactly as clicking a <Link> would. You can also pass navigation state, control history stack behaviour, and navigate backward.
useNavigate — Imperative Navigation
import { useNavigate } from "react-router-dom";
// ── Basic navigation ──────────────────────────────────────────────────────────
function PostCreateForm() {
const navigate = useNavigate();
async function handleSubmit(formData) {
try {
const newPost = await postsApi.create(formData);
// Navigate to the newly created post after saving
navigate(`/posts/${newPost.id}`);
} catch (err) {
setError(err.message);
}
}
// ...
}
// ── Replace history (no back button) ─────────────────────────────────────────
function LoginPage() {
const navigate = useNavigate();
async function handleLogin(credentials) {
await auth.login(credentials);
// Replace history so pressing back doesn't return to login
navigate("/dashboard", { replace: true });
}
}
// ── Navigate backward ─────────────────────────────────────────────────────────
function BackButton() {
const navigate = useNavigate();
return (
<button onClick={() => navigate(-1)}>← Back</button>
);
}
// ── Navigate with state ───────────────────────────────────────────────────────
function PostCard({ post }) {
const navigate = useNavigate();
function handleClick() {
navigate(`/posts/${post.id}`, {
state: { post }, // pass post data to avoid a fetch on the detail page
});
}
}
Note:
navigate(-1) goes back one step in the browser history — equivalent to pressing the browser’s back button. navigate(1) goes forward. navigate(-2) goes back two steps. Use these relative navigation values carefully: if the user arrived at the page via a direct URL (no previous history), navigate(-1) might exit the application to the browser’s previous tab. Always consider providing an explicit fallback: navigate(history.length > 1 ? -1 : "/").Tip: Passing navigation state with
navigate("/path", { state: { data } }) lets the destination component receive data without making an API call. The post list passes the full post object to the detail page so it renders immediately while optionally refetching for freshness. Access it with const { state } = useLocation(); const initialPost = state?.post;. State is not visible in the URL and is cleared if the user refreshes the page.Warning: Never call
useNavigate outside of a component or call navigate() during the render phase. navigate() should only be called in event handlers, effects (useEffect), and asynchronous callbacks — never directly in the component body. Calling navigate during render causes infinite render loops because navigation triggers a re-render, which triggers navigation again.Navigation After API Mutations
// ── After creating a post ─────────────────────────────────────────────────────
async function handleCreatePost(formData) {
const newPost = await postsApi.create(formData);
navigate(`/posts/${newPost.id}`, {
replace: true, // don't keep the /posts/new form in history
state: { post: newPost, justCreated: true },
});
}
// ── After deleting a post ─────────────────────────────────────────────────────
async function handleDeletePost(postId) {
if (!confirm("Delete this post?")) return;
await postsApi.delete(postId);
navigate("/dashboard", { replace: true });
}
// ── After logout ──────────────────────────────────────────────────────────────
async function handleLogout() {
await authApi.logout(refreshToken);
authStore.clear();
navigate("/", { replace: true }); // go home, clear all auth history
}
// ── Show a success message on the destination page using location state ────────
function PostDetailPage() {
const { state } = useLocation();
return (
<div>
{state?.justCreated && (
<div className="bg-green-50 text-green-700 px-4 py-2 rounded mb-4">
✓ Post created successfully!
</div>
)}
{/* post content */}
</div>
);
}
useLocation — Reading the Current URL
import { useLocation } from "react-router-dom";
function AnalyticsTracker() {
const location = useLocation();
useEffect(() => {
// Track page views when the route changes
analytics.pageView(location.pathname + location.search);
}, [location]); // re-run on route change
return null;
}
// location object:
// {
// pathname: "/posts/42", ← the path
// search: "?page=2", ← query string (with ?)
// hash: "", ← URL hash (#)
// state: { post: {...} }, ← navigation state
// key: "abc123", ← unique key for this entry
// }
Common Mistakes
Mistake 1 — Calling navigate() during render
❌ Wrong — causes infinite render loop:
function Component() {
const navigate = useNavigate();
navigate("/other"); // called during render → infinite loop!
return <div>...</div>;
}
✅ Correct — call in event handlers or useEffect:
useEffect(() => { navigate("/other"); }, []); // ✓ after render
Mistake 2 — Not using replace on post-auth navigation
❌ Wrong — login stays in history:
navigate("/dashboard"); // /login is still in history — back button returns to login
✅ Correct:
navigate("/dashboard", { replace: true }); // ✓ login entry replaced
Quick Reference
| Task | Code |
|---|---|
| Basic navigate | navigate("/path") |
| Replace history | navigate("/path", { replace: true }) |
| With state | navigate("/path", { state: { data } }) |
| Go back | navigate(-1) |
| Read current location | const { pathname, state } = useLocation() |
| Read navigation state | location.state?.fieldName |