Summary
Document that RequireAuth (and equivalent guards in other framework adapters) should be used in a way that causes it to re-mount on navigation, not as a persistent layout wrapper that stays mounted across route changes.
This is a documentation-only change — the library doesn't need code changes.
Problem
When RequireAuth is used as a layout wrapper (e.g., wrapping a React Router <Outlet />), it stays mounted across navigations. Token expiration is checked via isExpiredAt() at render time, but since nothing triggers a re-render on navigation, the expiration check doesn't re-run. This means an expired token isn't detected until an API call returns 401.
This is a consequence of the library being router-agnostic — RequireAuth doesn't subscribe to route changes, so it can't detect navigation.
Correct patterns (React/Preact)
Option A: Per-route wrapping
Use RequireAuth at the route level so it mounts/unmounts on each navigation:
// ✅ Correct — RequireAuth mounts fresh on each navigation
<Route path="clients" element={<RequireAuth><ClientsPage /></RequireAuth>} />
<Route path="users" element={<RequireAuth><UsersPage /></RequireAuth>} />
// ❌ Avoid — RequireAuth stays mounted, won't re-check expiration on navigation
<Route element={<RequireAuth><Outlet /></RequireAuth>}>
<Route path="clients" element={<ClientsPage />} />
<Route path="users" element={<UsersPage />} />
</Route>
Option B: Key-based remount with a layout wrapper
Use location.pathname as a key to force RequireAuth to remount on navigation. This allows using a single wrapper while keeping the library router-agnostic (the router coupling stays in consumer code):
import { useLocation, Outlet } from 'react-router';
import { RequireAuth } from 'oidc-js-react';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const location = useLocation();
return <RequireAuth key={location.pathname}>{children}</RequireAuth>;
}
// Usage — single wrapper, no per-route boilerplate
<Route element={<ProtectedRoute><AdminLayout /></ProtectedRoute>}>
<Route index element={<DashboardPage />} />
<Route path="clients" element={<ClientsPage />} />
</Route>
There is no flash of fallback on navigation when the token is valid — RequireAuth reads from the existing auth context on mount and renders children immediately.
Why this is needed
React (and Preact) optimize rendering by skipping components whose props/state/context haven't changed. Since RequireAuth only subscribes to the auth context (not the router context), and the auth state doesn't change when a token passively expires (expiresAt is the same number — only Date.now() moves past it), React reuses the last render output without re-evaluating isExpiredAt().
Other frameworks (Svelte, Vue, Angular, Lit) re-evaluate template expressions naturally on navigation, so this issue is React/Preact-specific.
Scope
- Update README / docs for
oidc-js-react and oidc-js-preact
- No code changes needed in the library
🤖 Generated with Claude Code
Summary
Document that
RequireAuth(and equivalent guards in other framework adapters) should be used in a way that causes it to re-mount on navigation, not as a persistent layout wrapper that stays mounted across route changes.This is a documentation-only change — the library doesn't need code changes.
Problem
When
RequireAuthis used as a layout wrapper (e.g., wrapping a React Router<Outlet />), it stays mounted across navigations. Token expiration is checked viaisExpiredAt()at render time, but since nothing triggers a re-render on navigation, the expiration check doesn't re-run. This means an expired token isn't detected until an API call returns 401.This is a consequence of the library being router-agnostic —
RequireAuthdoesn't subscribe to route changes, so it can't detect navigation.Correct patterns (React/Preact)
Option A: Per-route wrapping
Use
RequireAuthat the route level so it mounts/unmounts on each navigation:Option B: Key-based remount with a layout wrapper
Use
location.pathnameas akeyto force RequireAuth to remount on navigation. This allows using a single wrapper while keeping the library router-agnostic (the router coupling stays in consumer code):There is no flash of fallback on navigation when the token is valid —
RequireAuthreads from the existing auth context on mount and renders children immediately.Why this is needed
React (and Preact) optimize rendering by skipping components whose props/state/context haven't changed. Since
RequireAuthonly subscribes to the auth context (not the router context), and the auth state doesn't change when a token passively expires (expiresAtis the same number — onlyDate.now()moves past it), React reuses the last render output without re-evaluatingisExpiredAt().Other frameworks (Svelte, Vue, Angular, Lit) re-evaluate template expressions naturally on navigation, so this issue is React/Preact-specific.
Scope
oidc-js-reactandoidc-js-preact🤖 Generated with Claude Code