In React Router v7, when do you call useNavigate's navigate() and when do you render <Navigate>, and why must navigate() not run during render?
answer
- event handler versus rendered element
- Navigate calls navigate in an effect
- first-render call warns and is dropped
- replace, state, relative, delta
basics
~20 sCall navigate() from event handlers or effects after something happens, such as a save completing. Render <Navigate> when the render output itself should be a redirect. navigate() during a component's first render only logs a warning and is ignored.
solid answer
~40 s`useNavigate()` returns an imperative `navigate(to, options)` or `navigate(delta)`. Use it in event handlers and effects: after an async save resolves, on an inactivity timeout, after a wizard step. Options include `replace`, `state`, `relative`, `preventScrollReset`, `flushSync` and `viewTransition`. `<Navigate to>` is a component that resolves its target during render and calls `navigate` inside `useEffect`; it fits a render branch that says "this screen is really somewhere else", or a class component that cannot use hooks, though the docs recommend `useNavigate` where you can. Calling `navigate()` in the body of a component's first render hits a guard: React Router warns `You should call navigate() in a React.useEffect()` and returns without navigating. Rendering is supposed to be pure anyway. With a data router, `navigate` is referentially stable and returns a promise that resolves when the navigation completes.
code
tsx · 16 linesimport { Navigate, useNavigate } from "react-router";
export function EditProfile({ profile }: { profile: Profile | null }) {
const navigate = useNavigate();
// render decision: this screen has nothing to edit
if (!profile) return <Navigate to="/settings" replace />;
async function handleSubmit(values: ProfileInput) {
await saveProfile(values);
// event-driven: move on only after the save succeeds
navigate("/settings/profile", { replace: true, state: { saved: true } });
}
return <ProfileForm initial={profile} onSubmit={handleSubmit} />;
}go deeper
Recall that navigate() is for handlers after something happens, <Navigate> is a component that redirects when rendered, and navigate(-1) goes back.
Explain that <Navigate> calls navigate in an effect, that first-render navigate() calls are dropped with a warning, and what replace and state do.
Choose between the two in real flows, always pass replace for redirects, and use the data router's awaited navigate to sequence UI teardown.
Push redirect decisions to the earliest layer that knows them and keep components free of render-time side effects, so navigation stays predictable as the app grows.
## Two shapes of programmatic navigation React Router v7 offers two ways to navigate without the user clicking a link: - **`useNavigate()`**, a hook that returns a `navigate` function you call imperatively; - **`<Navigate>`**, a component that performs a navigation when it is rendered. They share the same path-resolution logic, so relative `to` values, `relative="path"`, `replace` and `state` behave identically. The difference is **when** the navigation is triggered. ## `navigate()` — call it when something happens `navigate` accepts either a target and options, or a number: | Call | Effect | |---|---| | `navigate("/settings/profile")` | push a new entry | | `navigate("..", { replace: true })` | replace the current entry, route-relative target | | `navigate("/done", { state: { savedAt } })` | attach state, readable as `useLocation().state` on arrival | | `navigate("?tab=2", { preventScrollReset: true })` | keep the scroll position (data and framework modes) | | `navigate(-1)` | go back one history entry, like the browser Back button | The React Router docs reserve it for navigations the user did not click: after a form submission completes, logging out after inactivity, timed flows. For something the user clicks, a `<Link>` gives better defaults (a real anchor, open-in-new-tab, accessibility). In a **data router** (`createBrowserRouter` + `RouterProvider`), `navigate` is a stable reference across navigations and returns a `Promise` that resolves once the navigation completes, including any loaders. In declarative mode (`<BrowserRouter>`) the function can change identity when the pathname changes and returns nothing, which is why the declared return type is `void | Promise<void>`. ## `<Navigate>` — a redirect as render output `<Navigate to="/settings/profile" replace />` renders `null`. During render it resolves the target against the route context; then, in a `useEffect`, it calls `navigate` with `replace`, `state` and `relative`. It suits code where the **render decision** is the redirect: - a route element that exists only to forward an old URL to its new home; - a branch like `if (!profile) return <Navigate to=".." replace />`; - class components, which cannot call hooks. Note that `replace` has no default on `<Navigate>`: without it, the redirect **pushes**, leaving the redirecting URL in history. For a redirect you almost always want `replace`. The docs recommend preferring `useNavigate` over the component where possible. ## Why `navigate()` must not run during render A React render must be pure: React may render a component and discard the result, or render twice under Strict Mode. Navigation is a side effect, so it belongs in an event handler or an effect. React Router enforces the most harmful case directly: 1. `useNavigate` sets an internal flag in a layout effect after the component first commits. 2. If `navigate()` is called before that flag is set, which is exactly the first render, it logs `You should call navigate() in a React.useEffect(), not when your component is first rendered.` 3. It then **returns without navigating**, because the router's listener is not wired to this component yet. So `if (saved) navigate("/done")` in a component body appears to do nothing on mount. The fixes are to move the call into the handler that set `saved`, into a `useEffect`, or to return `<Navigate>` from that branch. ## Choosing, in practice 1. The user clicked something whose purpose is to go somewhere: `<Link>`. 2. Something finished (a save, a timer, a server response) and the app decides to move: `navigate()` in that callback. 3. The component's render output *is* "go elsewhere": return `<Navigate replace />`. Redirects decided **before** rendering, from a loader or an action, are a separate mechanism and live with the data APIs. ## Mistakes interviewers listen for - **Redirecting with `<Navigate>` but no `replace`.** The redirecting URL stays in history and the Back button returns the user to a screen that immediately redirects again. - **Navigating before the async work succeeds.** `navigate()` placed before `await saveProfile()` leaves the page even when the save fails; call it after the awaited work, inside the success path. - **Putting `navigate` in an effect with missing dependencies.** An effect that should redirect when `status` becomes `"done"` must list `status`, or it fires once on mount and never again. - **Using `navigate(-1)` as a universal Back.** It follows browser history, which can point outside the app on a deep-linked page. ## Summary - `navigate()` is imperative: handlers and effects, with `replace`, `state`, `relative` and `navigate(-1)`. - `<Navigate>` navigates from an effect when rendered; pass `replace` for redirects. - First-render calls to `navigate()` warn and are dropped; keep renders pure.
- In React Router v7, how does the destination page read the state passed with navigate(to, { state })?Through `useLocation().state`. The state is stored with the history entry, so it survives Back and Forward within the tab, but it is absent when the URL is opened fresh or shared, so the page must render sensibly without it. Keep it small and serialisable.
- In React Router v7, can you await navigate() before closing a modal?With a data router, yes: `navigate` returns a promise that resolves when the navigation, including its loaders, has completed. In declarative mode the function returns nothing, so there is nothing to await; you would react to the new location in the next render instead.
saying these in an interview costs you the question
- Calling navigate() in the component body is fine because React Router queues it
- <Navigate> replaces the history entry by default
- useNavigate is the right choice for ordinary clickable menu items
- <Navigate> navigates synchronously during render
- navigate(-1) takes a path string argument for going back