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:
co-authored by
Claude Sonnet 4.6
parent
72977334fc
commit
ee04aee4fb
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user