feat(editor): move the admin directory onto TanStack Query - #7726
Merged
Conversation
People, Teams and Team details each fetched and held their own copy of the team list and the admin roster. One pass through those screens cost seven requests where three distinct resources were being read. They now read through shared query keys, and the write paths invalidate the cache instead of calling their own reload function, so a change made in one view is visible in the others. Measured over teams -> team details -> back -> people: 7 requests to 3, and 17 committed renders to 15.
The thirteen write handlers each did the same five things by hand: set a processing flag, call the service, toast the outcome, extract a message from an axios error, and reload their own slice of the data. They are now declarations against one helper that does all five, and each says which slices of the directory it invalidates. Doing this alongside the reads changes one earlier decision: invalidation is per operation rather than blanket, because a mutation is the natural place to record which resources a write disturbs. The blanket helper survives only for child components that write through their own services. Fixes the missing refresh after disabling a user's MFA, which left the row showing the old state and the menu item on screen.
jbrunton96
approved these changes
Aug 28, 2026
balazs-szucs
approved these changes
Aug 28, 2026
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.
Description of Changes
Step 5 of the TanStack Query rollout, covering the admin People, Teams and Team details screens. Follows #7264, #7283, #7285.
The problem
Two separate ones, in the same three files.
Reads. Each section fetched and held its own copy of the same resources: People read the roster and the team list, Teams read the team list plus the roster again when its add-member modal opened, Team details read all three. Cost scaled with how many screens you visited rather than with how much data exists.
Writes. Thirteen handlers each did the same five things by hand: set a processing flag, call the service, toast the outcome, dig a message out of an axios error, and reload their own slice. Refreshing was a convention, not a mechanism, and one handler had already forgotten it.
The fix
Three shared query keys (
adminUsers,teams,teamDetails), and oneuseAdminMutationhelper that every write is declared against:Each write names the slices it disturbs, which is the part that only works when reads and writes are designed together:
createTeaminvalidates the team list, while a membership move invalidates the list, both teams' detail rows and the roster, because it genuinely changes all three. Invalidation refetches only mounted queries, so this costs nothing extra.The blanket "invalidate everything" helper survives in exactly one role: child components (invite, password change, seat update) that write through their own services, where the affected scopes are not visible from the call site.
Why it is better, measured
Request counts come from one harness driving
teams -> team details -> back -> people, run against the branch point and against this branch. The assertion is committed, so it cannot silently regress.getTeamsgetUsersgetTeamDetailsThree is one per distinct resource, the floor for that sequence. The four
getTeamswere the Teams table, Team details fetching the same list for its "move to team" dropdown, the explicit refresh on the back button, and People.Renders barely move, which is expected: this changes where data lives, not how often React draws. It is reported because a caching change can quietly cost renders, and this one does not.
On the code itself, across the three sections:
useState/useEffectremovedisAxiosErrorblockssetProcessingcallsisAxiosErroris no longer imported by any of the three files.Bug fixed
disableMfaByAdminshowed a success toast and never refreshed. The menu item renders only whenuser.mfaEnabledis true, so an admin disabled MFA, was told it worked, and watched the option stay on screen until a manual reload. It is covered by a test that fails if the invalidation is removed.Behaviour worth checking in review
useState, so its row actions disable together as before.console.erroris kept, once, in the shared error path.Testing
Five tests, each verified by breaking the implementation and confirming that one test, and only that one, fails:
staleTime: 0)The write tests drive the real flows through their modals and menus rather than calling hooks directly.
task frontend:checkpasses typecheck, lint and oxfmt, and 2383 of 2385 editor tests. The two failures,workbenchSession.test.tsandnotificationActions.test.tsx, are untouched here and fail identically with this branch's changes reverted.Scope
The three services keep their current shape; nothing outside these three sections and the new hook module changes. Child modals that write through their own services still refresh via the blanket helper, and converting those is separate work.