Repository navigation
Fix login redirect loop: replace cached /me actor, don't just invalidate - #22
Merged
Merged
Conversation
Login succeeded server-side (204 + Set-Cookie) but the SPA bounced the user back to /login. Root cause: route `beforeLoad` guards use `ensureQueryData(meQueryOptions)`, which returns the cached value unconditionally. After the page-load /me probe failed (401, cached null), `useLogin.onSuccess` ran `invalidateQueries(["me"])` — which only marks the query stale and triggers a refetch *if there's an active observer*. The login form has none. So the cached null stuck around; beforeLoad saw it and redirected back to /login. `fetchQuery` would have hit the same trap, because `meQueryOptions` has `staleTime: Infinity` — fetchQuery returns cached when not stale. Fix: - `useLogin.onSuccess`: `removeQueries(["me"])` first to clear the cache, then `await fetchQuery(meQueryOptions)` to plant the fresh actor. The next ensureQueryData call (in beforeLoad) sees the new value synchronously. - `useLogout.onSettled`: `setQueryData(["me"], null)` directly. We already know the answer; no need for a round trip. Tests (2 new, 23 total): - `useLogin > plants the fresh /me actor in the cache so route guards see it` — explicitly seeds null, runs the mutation, asserts the cache holds the new actor on settle. Locks in the regression. - `useLogout > clears the cached actor on success so the login guard takes over`. Verified against a live Docker container: pre-login /me → 401, login → 204 + cookie, post-login /me with same jar → 200 with actor. The frontend was the only thing broken.
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
Login succeeded server-side (204 +
Set-Cookie) but the SPA bounced the user back to/login. Root cause is purely frontend cache management.Trace:
useMequery runs → 401 → cached value isnull.useLogin.onSuccessraninvalidateQueries(["me"])— but with no active observer (the login form doesn't useuseMe), this only marks the query stale. No refetch.navigate({ to: "/" })fires →/route'sbeforeLoadcallsensureQueryData(meQueryOptions).ensureQueryDatareturns the cachednullregardless of stale flag./login.fetchQuerywould hit the same trap becausemeQueryOptionshasstaleTime: Infinity— fetchQuery returns cached when not stale.Fix:
useLogin.onSuccess—removeQueries(["me"])to clear the cache, thenawait fetchQuery(meQueryOptions)to plant the fresh actor. NextensureQueryDatacall sees the new value synchronously.useLogout.onSettled—setQueryData(["me"], null)directly. We already know the answer.Test plan
/me→ 401, login → 204 + cookie, post-login/mewith same jar → 200 with actor. The backend was correct; only the frontend cache was the issue.