Programmatic Navigation — useNavigate and Redirects

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

🧠 Test Yourself

After a user creates a post and you call navigate("/posts/42") (without replace), they press the browser back button. Where do they go?