跪拜 Guibai
← All articles
Frontend

React Router Auth Guards: The children Pattern, Redirects, and History Cleanup

By 凌涘 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The children-based guard pattern decouples access control from page components, so a single ProtectRoute can wrap any number of protected routes without duplicating logic. Pairing replace on both the guard redirect and the post-login navigation eliminates the back-button loop that plagues naive login flows.

Summary

ProtectRoute wraps any component with an authentication check, reading a login flag from localStorage and returning a <Navigate> redirect when the check fails. The redirect carries the original path in location.state so the login form can send the user back to exactly where they were headed. Login uses useNavigate with replace: true to overwrite the login page's history entry, preventing the back button from landing on an already-authenticated login screen that would immediately redirect again. The same guard pattern extends beyond authentication to any pre-entry check: role permissions, feature flags, or A/B experiment routing.

Takeaways
ProtectRoute receives children via props and returns them only when the user is authenticated, keeping guard logic independent of any specific page component.
Authentication state is stored in localStorage as a simple string flag, making the demo self-contained without a backend session.
When unauthenticated, ProtectRoute renders <Navigate to="/login" replace state={{ from: location.pathname }} />, which both redirects and preserves the intended destination.
The login form reads location.state?.from with optional chaining and a fallback to '/', covering both redirected and direct visits in a single expression.
After login, navigate(from, { replace: true }) overwrites the login page in history so the back button skips it entirely.
<Navigate> provides declarative redirects inside JSX; useNavigate() provides programmatic redirects inside event handlers and callbacks.
The full flow chains guard, redirect, state passing, login, redirect-back, and history replacement into a seamless access-control loop.
Conclusions

Using localStorage for auth state is convenient for demos but sidesteps the token-refresh and session-expiry problems that real production guards must handle.

The children pattern makes ProtectRoute a transparent wrapper: it imposes no props contract on the components it protects, so any page can be dropped inside without modification.

replace on both sides of the login flow is the detail that prevents the frustrating back-button loop; omitting it is a common mistake in hand-rolled auth routing.

The same guard structure applies to any pre-render check, not just auth. A feature-flag guard or an A/B test splitter would follow the identical children + conditional Navigate pattern.

Concepts & terms
Route Guard
A component that wraps a route's element and conditionally renders either the protected content or a redirect, based on some runtime check like authentication status.
children pattern (React)
Passing JSX content between a component's opening and closing tags, received via the children prop. It allows wrapper components to render arbitrary content without knowing what it is.
replace in navigation
A navigation option that substitutes the current history entry with the new URL instead of pushing a new entry onto the stack. The back button then skips the replaced page entirely.
location.state
A property on React Router's location object that carries arbitrary data between routes during navigation. It is not visible in the URL and persists only in memory.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗