Skip to content

Commit 7b5f37b

Browse files
committed
♻️(frontend) refactor thread query cache management
The thread query is an infinite one and the frontend logic is based on the structuralSharing concept of react-query to optimiscally update the react query cache on thread mutation in order to improve ux. This part is a tricky one and it's easy to introduce regression, that's why refactor it by moving the corresponding logic into a mailbox-cache module and battle test it.
1 parent 7a0f6fb commit 7b5f37b

6 files changed

Lines changed: 836 additions & 152 deletions

File tree

src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,15 @@ const ThreadPanelTitle = ({ selectedThreadIds, isAllSelected, isSomeSelected, is
184184
<Tooltip content={mainReadTooltip}>
185185
<Button
186186
onClick={() => {
187+
// Close the open thread before firing the mutation. Waiting for
188+
// onSuccess would let the visibility observer re-observe the
189+
// newly-unread messages and debounce a mark-as-read that silently
190+
// reverts the action.
191+
unselectThread();
192+
onClearSelection();
187193
markAsReadAt({
188194
threadIds: threadIdsToMark,
189195
readAt: selectionReadStatus === SelectionReadStatus.READ ? null : new Date().toISOString(),
190-
onSuccess: () => {
191-
unselectThread();
192-
onClearSelection();
193-
}
194196
});
195197
}}
196198
icon={<Icon name={selectionReadStatus === SelectionReadStatus.READ ? 'mark_email_unread' : 'mark_email_read'} type={IconType.OUTLINED} />}
@@ -304,13 +306,13 @@ const ThreadPanelTitle = ({ selectedThreadIds, isAllSelected, isSomeSelected, is
304306
label: markAllUnreadLabel,
305307
icon: <span className="material-icons">mark_email_unread</span>,
306308
callback: () => {
309+
// Close the open thread before the mutation so the visibility
310+
// observer cannot re-mark the newly-unread messages as read.
311+
unselectThread();
312+
onClearSelection();
307313
markAsReadAt({
308314
threadIds: threadIdsToMark,
309315
readAt: null,
310-
onSuccess: () => {
311-
unselectThread();
312-
onClearSelection();
313-
}
314316
});
315317
},
316318
}] : []),

src/frontend/src/features/layouts/components/thread-view/components/thread-action-bar/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,14 @@ export const ThreadActionBar = ({ canUndelete, canUnarchive }: ThreadActionBarPr
184184
{
185185
label: t('Mark as unread'),
186186
icon: <Icon name="mark_email_unread" type={IconType.OUTLINED} />,
187-
callback: () => markAsReadAt({ threadIds: [selectedThread!.id], readAt: null, onSuccess: unselectThread })
187+
// Unmount the thread view before firing the mutation. If we waited for
188+
// onSuccess, the cache patch would re-flag visible messages as unread
189+
// while the view is still mounted, letting the visibility observer
190+
// debounce a mark-as-read request that silently reverts this action.
191+
callback: () => {
192+
unselectThread();
193+
markAsReadAt({ threadIds: [selectedThread!.id], readAt: null });
194+
},
188195
},
189196
...(canLeaveThread ? [{
190197
label: t('Leave this thread'),

src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-actions.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,12 @@ const ThreadMessageActions = ({
5757
const toggleReadStateFrom = useCallback((is_unread: boolean) => {
5858
if (!selectedThread) return;
5959
if (is_unread) {
60-
// Mark as unread from here: subtract 1ms so this message becomes unread
60+
// Mark as unread from here: subtract 1ms so this message becomes unread.
61+
// Unmount the thread view before the mutation so the visibility observer
62+
// cannot debounce a mark-as-read request on the newly-unread messages.
6163
const readAt = new Date(new Date(message.created_at!).getTime() - 1).toISOString();
62-
markAsReadAt({ threadIds: [selectedThread.id], readAt, onSuccess: unselectThread });
64+
unselectThread();
65+
markAsReadAt({ threadIds: [selectedThread.id], readAt });
6366
} else {
6467
// Mark as read from here: read up to this message's created_at
6568
markAsReadAt({ threadIds: [selectedThread.id], readAt: message.created_at! });

0 commit comments

Comments
 (0)