fix(20): CR-02 derive editorMember from live query data

Store only the member id in editorMemberId state rather than the full
AdminMember object. Derive editorMember from membersQuery.data after each
render, so query invalidations following a per-section save always give
MemberEditorSheet fresh server state — eliminating the stale-reference
silent-overwrite race.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lucas Berger
2026-06-18 18:00:47 -04:00
co-authored by Claude Sonnet 4.6
parent 72977334fc
commit ee04aee4fb
+14 -3
View File
@@ -80,7 +80,11 @@ export function AdminPage() {
// create ("Add member" trigger) modes (D-07 unification). // create ("Add member" trigger) modes (D-07 unification).
const [editorOpen, setEditorOpen] = useState(false); const [editorOpen, setEditorOpen] = useState(false);
const [editorMode, setEditorMode] = useState<'edit' | 'create'>('edit'); const [editorMode, setEditorMode] = useState<'edit' | 'create'>('edit');
const [editorMember, setEditorMember] = useState<AdminMember | null>(null); // CR-02: store only the id, not the full member object. The full member is
// derived from live query data each render (see below, after membersQuery),
// so per-section saves + invalidations keep the editor's member prop fresh
// without a stale captured reference.
const [editorMemberId, setEditorMemberId] = useState<number | null>(null);
// Ref to the element that opened the sheet — focus returns here on close (a11y) // Ref to the element that opened the sheet — focus returns here on close (a11y)
const editorTriggerRef = useRef<HTMLElement | null>(null); const editorTriggerRef = useRef<HTMLElement | null>(null);
// Ref for the "Add member" ghost trigger button // Ref for the "Add member" ghost trigger button
@@ -115,6 +119,13 @@ export function AdminPage() {
staleTime: 60 * 1000, staleTime: 60 * 1000,
}); });
// CR-02: derive editorMember from live query data so saves + invalidations always
// reflect the latest server state rather than a snapshot from when the row was tapped.
const editorMember =
editorMemberId !== null
? (membersQuery.data?.members.find((m) => m.id === editorMemberId) ?? null)
: null;
// Calendars query // Calendars query
const calendarsQuery = useQuery({ const calendarsQuery = useQuery({
queryKey: ['admin', 'calendars'], queryKey: ['admin', 'calendars'],
@@ -240,7 +251,7 @@ export function AdminPage() {
// Open the editor for a specific member (row tap → edit mode) // Open the editor for a specific member (row tap → edit mode)
function openEditorForMember(member: AdminMember, rowEl: HTMLElement) { function openEditorForMember(member: AdminMember, rowEl: HTMLElement) {
editorTriggerRef.current = rowEl; editorTriggerRef.current = rowEl;
setEditorMember(member); setEditorMemberId(member.id); // CR-02: store id only; editorMember is derived from live data
setEditorMode('edit'); setEditorMode('edit');
setEditorOpen(true); setEditorOpen(true);
} }
@@ -248,7 +259,7 @@ export function AdminPage() {
// Open the editor in create mode ("Add member" trigger) // Open the editor in create mode ("Add member" trigger)
function openEditorForCreate() { function openEditorForCreate() {
editorTriggerRef.current = addMemberTriggerRef.current; editorTriggerRef.current = addMemberTriggerRef.current;
setEditorMember(null); setEditorMemberId(null);
setEditorMode('create'); setEditorMode('create');
setEditorOpen(true); setEditorOpen(true);
} }