Admin permissions toggles silently revert after navigation (TanStack Query cache stale after save)
just an FYI
----
Summary
Toggling any permission on /admin/settings/permissions (Anonymous voting, Public view, Submissions, etc.) appears to save successfully — no error in UI or logs — but the new value doesn't reflect when navigating away and back to the page. A hard browser refresh (Cmd+R) shows the correct saved value, confirming the DB write succeeded; the UI is reading stale data.
Reproduce
- Go to
/admin/settings/permissionsas admin - Toggle "Anonymous voting" OFF
- Navigate to another admin page (e.g.
/admin/settings) - Navigate back to
/admin/settings/permissions - Toggle appears ON again
- Hard browser refresh — toggle now correctly shows OFF
Verification: DB write succeeds
SELECT (portal_config::jsonb -> 'features' ->> 'anonymousVoting') FROM settings;
-- Returns 'false' after toggle, as expected
So the persistence layer is fine. The bug is purely client-side cache.
Root cause
In apps/web/src/routes/admin/settings.permissions.tsx, updateFeature():
await updatePortalConfigFn({ data: { features: { [key]: value } } })
startTransition(() => {
router.invalidate()
})
router.invalidate() re-runs the route loader, but the loader uses queryClient.ensureQueryData(settingsQueries.portalConfig()). With staleTime: STALE_TIME_LONG on the query, ensureQueryData returns the cached value — the stale pre-save one — instead of refetching.
useState(features?.anonymousVoting ?? true) then initializes from the stale cache → toggle reads as ON.
Suggested fix
Invalidate the specific TanStack Query after the save:
await updatePortalConfigFn({ data: { features: { [key]: value } } })
await queryClient.invalidateQueries({ queryKey: ['settings', 'portalConfig'] })
startTransition(() => {
router.invalidate()
})
Requires useQueryClient import + const queryClient = useQueryClient() in the component. Verified locally — fully resolves the issue.
Scope (probably affects other pages too)
The same await mutation + router.invalidate() pattern without explicit queryClient.invalidateQueries likely exists on other admin pages that use useSuspenseQuery against a long-staleTime query. Worth auditing:
admin/settings/portal(welcome card)admin/settings/brandingadmin/settings/widgetadmin/settings/help-center
Environment
- Quackback v0.10.5 (self-hosted Docker)
- Reproduces deterministically — not a race condition
0 Comments
Sign in to comment
No comments yet. Be the first to share your thoughts!
