diff --git a/src/frontend/src/features/layouts/components/thread-view/components/thread-message/index.tsx b/src/frontend/src/features/layouts/components/thread-view/components/thread-message/index.tsx index 3bdfb472..60e4e86b 100644 --- a/src/frontend/src/features/layouts/components/thread-view/components/thread-message/index.tsx +++ b/src/frontend/src/features/layouts/components/thread-view/components/thread-message/index.tsx @@ -38,7 +38,7 @@ export const ThreadMessage = forwardRef( const replyFormRef = useRef(null); const threadViewContext = useThreadViewContext() const isMessageReady = threadViewContext.isMessageReady(message.id); - const [isMessageBodyLoaded, setIsMessageBodyLoaded] = useState(false); + const [isMessageBodyLoaded, setIsMessageBodyLoaded] = useState(isMessageReady); const [isFolded, setIsFolded] = useState(!isLatest && !message.is_unread && !draftMessage?.is_draft); const [replyFormMode, setReplyFormMode] = useState(getReplyFormMode) const previousReplyFormMode = usePrevious(replyFormMode); @@ -97,10 +97,6 @@ export const ThreadMessage = forwardRef( setReplyFormMode(getReplyFormMode()) }, [message, draftMessage]) - useEffect(() => { - setIsFolded(!isLatest && !message.is_unread && !draftMessage?.is_draft); - }, [isLatest, draftMessage?.is_draft]) - // Smooth scroll to the reply form when it is opened by the user useEffect(() => { if (!threadViewContext.isReady) return; diff --git a/src/frontend/src/features/layouts/components/thread-view/index.tsx b/src/frontend/src/features/layouts/components/thread-view/index.tsx index bdb91508..6835c828 100644 --- a/src/frontend/src/features/layouts/components/thread-view/index.tsx +++ b/src/frontend/src/features/layouts/components/thread-view/index.tsx @@ -42,7 +42,7 @@ const ThreadViewComponent = ({ messages, mailboxId, thread, showTrashedMessages, const rootRef = useRef(null); const { markAsRead } = useRead(); const isAISummaryEnabled = useFeatureFlag(FEATURE_KEYS.AI_SUMMARY); - const { isReady, reset } = useThreadViewContext(); + const { isReady, reset, hasBeenInitialized, setHasBeenInitialized } = useThreadViewContext(); // Refs for all unread messages const unreadRefs = useRef>({}); // Find all unread message IDs @@ -95,7 +95,7 @@ const ThreadViewComponent = ({ messages, mailboxId, thread, showTrashedMessages, }, [isReady, unreadMessageIds.join(","), thread.id]); useEffect(() => { - if (isReady) { + if (isReady && !hasBeenInitialized) { let messageToScroll = latestMessage?.id; let selector = `#thread-message-${messageToScroll}`; if (draftMessageIds.length > 0) { @@ -109,15 +109,15 @@ const ThreadViewComponent = ({ messages, mailboxId, thread, showTrashedMessages, const el = document.querySelector(selector); if (el) { rootRef.current?.scrollTo({ top: el.offsetTop - 225, behavior: 'instant' }); + setHasBeenInitialized(true); } } }, [isReady]); - useEffect(() => { + useEffect(() => () => { reset(); }, [thread.id]); - return (
diff --git a/src/frontend/src/features/layouts/components/thread-view/provider.tsx b/src/frontend/src/features/layouts/components/thread-view/provider.tsx index 2bbaefdd..5814b839 100644 --- a/src/frontend/src/features/layouts/components/thread-view/provider.tsx +++ b/src/frontend/src/features/layouts/components/thread-view/provider.tsx @@ -9,6 +9,8 @@ type ThreadViewContextType = { isMessageReady: (messageId: string) => boolean | undefined; setMessageReadiness: (messageId: string, isReady: boolean) => void; reset: (messageId?: string) => void; + hasBeenInitialized: boolean; + setHasBeenInitialized: (hasBeenInitialized: boolean) => void; } const ThreadViewContext = createContext(undefined); @@ -19,6 +21,7 @@ const ThreadViewContext = createContext(undef */ const ThreadViewProvider = ({ messageIds, children }: ThreadViewProviderProps) => { const [messagesReadiness, setMessagesReadiness] = useState(new Map(messageIds.map((id) => [id, false]))); + const [hasBeenInitialized, setHasBeenInitialized] = useState(false); const isReady = useMemo(() => { return Array.from(messagesReadiness.values()).every((isReady) => isReady === true); @@ -48,6 +51,7 @@ const ThreadViewProvider = ({ messageIds, children }: ThreadViewProviderProps) = setMessageReadiness(messageId, false); } else { setMessagesReadiness(new Map(messageIds.map((id) => [id, false]))); + setHasBeenInitialized(false); } } @@ -56,8 +60,10 @@ const ThreadViewProvider = ({ messageIds, children }: ThreadViewProviderProps) = isMessageReady, setMessageReadiness, reset, + hasBeenInitialized, + setHasBeenInitialized, messagesReadiness, - }), [isReady, setMessageReadiness, isMessageReady, reset, messagesReadiness]); + }), [isReady, setMessageReadiness, isMessageReady, reset, messagesReadiness, hasBeenInitialized, setHasBeenInitialized]); diff --git a/src/frontend/src/features/providers/mailbox.tsx b/src/frontend/src/features/providers/mailbox.tsx index 0dfde9d4..45404983 100644 --- a/src/frontend/src/features/providers/mailbox.tsx +++ b/src/frontend/src/features/providers/mailbox.tsx @@ -122,7 +122,7 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { ?? mailboxQuery.data.data[mailboxQuery.data.data.length - 1] }, [router.query.mailboxId, mailboxQuery.data]) - const previousUnreadMessagesCount = usePrevious(selectedMailbox?.count_unread_messages || 0); + const previousUnreadMessagesCount = usePrevious(selectedMailbox?.count_unread_messages); const threadQueryKey = useMemo(() => { const queryKey = ['threads', selectedMailbox?.id]; if (searchParams.get('search')) { @@ -167,6 +167,7 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { const threadId = router.query.threadId; return threadsQuery.data?.pages.flatMap((page) => page.data.results).find((thread) => thread.id === threadId) ?? null; }, [router.query.threadId, flattenThreads]) + const previousSelectedThreadMessagesCount = usePrevious(selectedThread?.messages.length); const messagesQuery = useMessagesList({ query: { @@ -335,13 +336,21 @@ export const MailboxProvider = ({ children }: PropsWithChildren) => { // Invalidate the threads query to refresh the threads list when the unread messages count changes useEffect(() => { - if (!selectedMailbox) return; - if ((previousUnreadMessagesCount ?? 0) !== (selectedMailbox.count_unread_messages)) { + if (!selectedMailbox || previousUnreadMessagesCount === undefined) return; + if (previousUnreadMessagesCount !== selectedMailbox.count_unread_messages) { invalidateThreadsStats(); queryClient.invalidateQueries({ queryKey: ['threads', selectedMailbox?.id] }); } }, [selectedMailbox?.count_unread_messages]); + // Invalidate the thread messages query to refresh the thread messages when there is a new message + useEffect(() => { + if (!selectedThread || previousSelectedThreadMessagesCount === undefined) return; + if (previousSelectedThreadMessagesCount < (selectedThread?.messages.length ?? 0)) { + invalidateThreadMessages(); + } + }, [selectedThread?.messages.length]); + useEffect(() => { if (searchParams.get('search') !== previousSearchParams?.get('search')) { resetSearchQueryDebounced(); diff --git a/src/frontend/src/hooks/use-previous.ts b/src/frontend/src/hooks/use-previous.ts index 4b00f705..ae0a193b 100644 --- a/src/frontend/src/hooks/use-previous.ts +++ b/src/frontend/src/hooks/use-previous.ts @@ -5,7 +5,7 @@ import { useEffect, useRef } from 'react'; * @param value The value to track * @returns The previous value of the tracked variable */ -function usePrevious(value: T): T | undefined { +function usePrevious(value: T): T { const ref = useRef(value); useEffect(() => { @@ -15,4 +15,4 @@ function usePrevious(value: T): T | undefined { return ref.current; } -export default usePrevious; \ No newline at end of file +export default usePrevious;