fix(AuthProvider): split guardComponent into loadingComponent + guardComponent#269
Closed
erickteowarang wants to merge 9 commits into
Closed
fix(AuthProvider): split guardComponent into loadingComponent + guardComponent#269erickteowarang wants to merge 9 commits into
erickteowarang wants to merge 9 commits into
Conversation
…Component Previously `guardComponent` rendered for the union of `!isReady` and `!isAuthenticated`, so any sign-in screen wired into that slot would flash on every reload before the session was known — contradicting the canonical `useAuth` pattern documented in the SDK itself. Add `loadingComponent` for the `!isReady` state and narrow `guardComponent` to fire only when `isReady && !isAuthenticated`. OAuth callback child-suppression now keys off `loadingComponent` (the slot that owns the `!isReady` state). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
/review |
…gin is on `loadingComponent` and `guardComponent` are both optional. Previously, omitting one meant children rendered through during that state — which, combined with `autoLogin`, briefly showed protected UI before the redirect fired. Couple the "hide on omission" default to `autoLogin`: when it is on, an unset slot renders nothing so protected UI never flashes; when it is off, children continue to render so the `useAuthSuspense` and public-app patterns still work without any new props. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
commit: |
`pnpm fmt:check` failed CI on the props table. Also point the `useAuthSuspense` cross-reference at the correct heading slug (`#suspense-compatible-hook`, not `#suspense-integration`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous callback suppression set `resolvedChildren` to `null` but still rendered `AuthGuard`. If the auth client surfaced a stale `isReady && !isAuthenticated` state while a callback exchange was in flight, `AuthGuard` would render `guardComponent` (the sign-in screen) — flashing the very UI the user just came back from. Bypass `AuthGuard` entirely during the blackout window. Providing `loadingComponent` still opts the consumer out of the blackout, so the "trust me, I'll handle transitions" contract is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IzumiSy
reviewed
May 18, 2026
…ok scope Invoking `loadingComponent()` / `guardComponent()` as plain function calls inlined the slot's hooks into AuthGuard's own hook list. Because the calls are gated on auth state, the hook order changed across renders the moment a consumer passed a slot that used a hook itself (e.g. a sign-in screen calling `useAuth`) — tripping React's rules of hooks. Render each slot via `createElement` so it becomes its own fiber. The existing tests missed this because their slot components were stateless; add a regression test that asserts no hook-order warnings when the slot calls `useAuth` and the auth state transitions out of it. Reported by IzumiSy in #269. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the auth Playwright suite with a regression test that walks the full login flow and asserts: - the auth guard is not in the DOM after the callback resolves (no sign-in flash during the OAuth callback exchange), and - no React hook-order warnings are logged during the auth state transition (the E2E app's <AuthGuard> uses `useAuth`, the exact shape that previously inlined a hook into AuthGuard's hook list). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to the existing "maintains session on page reload" test: that one verifies the authenticated content is eventually visible after reload, but does not catch a single-frame guardComponent flash during the `!isReady` window. Inject a MutationObserver via `addInitScript` so it is attached before any post-reload render, and assert the auth-guard testid never enters the DOM while the session is being re-checked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Closing this as I've determined that the original issue is more related to DT and erp-kit than app-shell |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AuthProvider's singleguardComponentslot rendered for the union of!isReadyand!isAuthenticated. Any sign-in screen wired into that slot — the obvious thing to do, and what the type name implies — flashes on every reload before the session is known. This contradicts the canonical pattern documented inuseAuth's own JSDoc.loadingComponentfor the!isReadystate. NarrowguardComponentto fire only whenisReady && !isAuthenticated(sign-in screens no longer flash).loadingComponent— the slot that explicitly owns the!isReadystate.autoLoginis enabled and a slot is omitted, the protected tree is hidden during that state instead of briefly rendering children. WhenautoLoginis off, children render in those windows — preserving theuseAuthSuspensepattern (where a<Suspense>boundary inside the tree owns the loading UI) and public-app cases.Demo here: https://www.loom.com/share/d71ac5293e3d45039e795bd675fa8abb
Default behavior matrix (no slots passed)
autoLogin!isReadyisReady && !isAuthenticatedtruefalse/ unsetMigration
If you were passing a loading UI to
guardComponent, rename toloadingComponent. If you were passing a sign-in screen, keep it onguardComponent— it will no longer flash before the auth check resolves.Test plan
pnpm type-checkcleanpnpm lint0 warnings / 0 errorspnpm test— 1015 tests pass, including:guardComponentdoes NOT render during!isReadyloadingComponentrenders during!isReadyloadingComponentautoLoginhides children during!isReadywhen noloadingComponentis setautoLoginhides children during!isAuthenticatedwhen noguardComponentis setautoLogin, children render during!isReady(Suspense pattern preserved)pnpm fmtapplied.changeset/auth-loading-component.md,minor)docs/concepts/authentication.md)🤖 Generated with Claude Code