跪拜 Guibai
← All articles
Frontend

How React Router Guards, Redirects, and Remembers Where Users Were Going

By 小月土星 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Getting the history stack wrong during auth redirects creates infinite back-button loops that trap users. The `replace` + `location.state` pattern shown here is the standard fix, and the same approach works for any conditional navigation — expired sessions, multi-step wizards, or post-payment flows.

Summary

Route redirection in React Router is a full design pattern, not a simple jump. Three real-world scenarios — campaign teardowns, path normalization, and conditional auth redirects — each demand careful use of `replace` to avoid trapping users in history-stack loops. The `Navigate` component carries state through `location.state`, an invisible channel that survives client-side navigation but never touches the server.

The ProtectRoute component acts as a gate: it reads `localStorage` for a login flag, and when that flag is absent, it redirects to `/login` while embedding the current `location` object into the navigation state. This single object preserves the full pathname, query string, and hash, so a user arriving at `/pay?coupon=SUMMER50` returns to exactly that URL after authenticating.

A complete timeline traces the full loop — from URL entry through guard interception, login, and final render of the protected Pay component. The design separates concerns cleanly: business pages carry zero auth logic, the guard centralizes permission checks, and the login form uses `FormData` to avoid unnecessary re-renders. The entire mechanism relies on two implicit data channels: `localStorage` for the login flag and `location.state` for the user's original intent.

Takeaways
Use `replace` on every auth redirect to prevent the login page from polluting the browser history stack.
Pass the full `location` object via `state`, not just `pathname`, so query parameters and hashes survive the round-trip.
Centralize auth gating in a single `ProtectRoute` wrapper component instead of scattering login checks across pages.
Read the return URL in the login component with `location.state?.from?.pathname || '/'` to default safely to the homepage.
Prefer `FormData` over controlled inputs for simple login forms to cut out unnecessary re-renders.
Never trust client-side `localStorage` alone for real auth; the backend must validate every protected request.
Conclusions

The `replace` vs `push` distinction is the single most overlooked detail in auth redirects, and getting it wrong creates a silent UX bug that QA often misses.

Carrying the entire `location` object rather than a bare path string is a defensive habit that costs nothing and saves coupon parameters, UTM tags, and deep-link state.

The `children` prop pattern in ProtectRoute is a textbook example of separation of concerns: the guard knows nothing about what it protects, and the page knows nothing about auth.

Using `FormData` for login is a pragmatic trade-off — it sacrifices real-time validation for simplicity, which is the right call when the form has two fields and no dynamic validation rules.

Concepts & terms
location.state
A React Router mechanism for passing data between routes without exposing it in the URL. It travels via the browser's History API and is only visible on the client side.
replace vs push navigation
`push` adds a new entry to the browser history stack; `replace` overwrites the current entry. Using `replace` during redirects prevents users from getting stuck in back-button loops.
ProtectRoute (route guard)
A wrapper component that checks an auth condition (e.g., a localStorage flag) before rendering its children. If the check fails, it redirects to a login page while saving the intended destination in location.state.
FormData API
A browser-native API for reading form field values without controlled React state. It avoids re-renders on every keystroke and is suitable for simple forms that don't need real-time validation.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗