Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/perf-infinite-query-message-tabs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

perf: use useInfiniteQuery to prevent UI freeze in message tabs
47 changes: 34 additions & 13 deletions apps/meteor/client/views/room/contextualBar/MentionsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,53 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useInfiniteQuery } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import MessageListTab from './MessageListTab';
import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
import { useRoom } from '../contexts/RoomContext';

const COUNT = 50;

const MentionsTab = (): ReactElement => {
const getMentionedMessages = useEndpoint('GET', '/v1/chat.getMentionedMessages');

const room = useRoom();

const mentionedMessagesQueryResult = useQuery({
const mentionedMessagesQueryResult = useInfiniteQuery({
queryKey: ['rooms', room._id, 'mentioned-messages'] as const,

queryFn: async () => {
const messages: IMessage[] = [];
queryFn: async ({ pageParam }) => {
const result = await getMentionedMessages({ roomId: room._id, offset: pageParam, count: COUNT });
return {
messages: result.messages.map(mapMessageFromApi),
total: result.total,
count: result.count,
offset: pageParam,
};
},

for (
let offset = 0, result = await getMentionedMessages({ roomId: room._id, offset: 0 });
result.count > 0;
offset += result.count, result = await getMentionedMessages({ roomId: room._id, offset })
) {
messages.push(...result.messages.map(mapMessageFromApi));
}
initialPageParam: 0,

return messages;
getNextPageParam: (lastPage) => {
const nextOffset = lastPage.offset + lastPage.count;
return nextOffset < lastPage.total ? nextOffset : undefined;
},
});

const messages = useMemo(
() => mentionedMessagesQueryResult.data?.pages.flatMap((page) => page.messages) ?? [],
[mentionedMessagesQueryResult.data],
);

const handleEndReached = useCallback(() => {
if (mentionedMessagesQueryResult.hasNextPage && !mentionedMessagesQueryResult.isFetching) {
mentionedMessagesQueryResult.fetchNextPage();
}
}, [mentionedMessagesQueryResult]);
Comment thread
TasfinMahmud marked this conversation as resolved.

const { t } = useTranslation();

return (
Expand All @@ -39,7 +56,11 @@ const MentionsTab = (): ReactElement => {
title={t('Mentions')}
emptyResultMessage={t('No_mentions_found')}
context='mentions'
queryResult={mentionedMessagesQueryResult}
messages={messages}
isLoading={mentionedMessagesQueryResult.isLoading}
isSuccess={mentionedMessagesQueryResult.isSuccess}
isFetchingNextPage={mentionedMessagesQueryResult.isFetchingNextPage}
onEndReached={handleEndReached}
/>
);
};
Expand Down
29 changes: 19 additions & 10 deletions apps/meteor/client/views/room/contextualBar/MessageListTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
ContextualbarDialog,
} from '@rocket.chat/ui-client';
import { useUserPreference, useRoomToolbox } from '@rocket.chat/ui-contexts';
import type { UseQueryResult } from '@tanstack/react-query';
import type { ReactElement, ReactNode } from 'react';
import { useCallback } from 'react';
import { Virtuoso } from 'react-virtuoso';
Expand All @@ -32,10 +31,14 @@ type MessageListTabProps = {
title: ReactNode;
emptyResultMessage: string;
context: MessageActionContext;
queryResult: UseQueryResult<IMessage[]>;
messages: IMessage[];
isLoading: boolean;
isSuccess: boolean;
isFetchingNextPage?: boolean;
onEndReached?: () => void;
};

const MessageListTab = ({ iconName, title, emptyResultMessage, context, queryResult }: MessageListTabProps): ReactElement => {
const MessageListTab = ({ iconName, title, emptyResultMessage, context, messages, isLoading, isSuccess, isFetchingNextPage, onEndReached }: MessageListTabProps): ReactElement => {
const formatDate = useFormatDate();
const showUserAvatar = !!useUserPreference<boolean>('displayAvatars');

Expand All @@ -54,26 +57,27 @@ const MessageListTab = ({ iconName, title, emptyResultMessage, context, queryRes
<ContextualbarClose onClick={handleTabBarCloseButtonClick} />
</ContextualbarHeader>
<ContextualbarContent flexShrink={1} flexGrow={1} paddingInline={0}>
{queryResult.isLoading && (
{isLoading && (
<Box paddingInline={24} paddingBlock={12}>
<Throbber size='x12' />
</Box>
)}
{queryResult.isSuccess && (
{isSuccess && (
<>
{queryResult.data.length === 0 && <ContextualbarEmptyContent title={emptyResultMessage} />}
{messages.length === 0 && <ContextualbarEmptyContent title={emptyResultMessage} />}

{queryResult.data.length > 0 && (
{messages.length > 0 && (
<MessageListErrorBoundary>
<MessageListProvider>
<Box is='section' display='flex' flexDirection='column' flexGrow={1} flexShrink={1} flexBasis='auto' height='full'>
<VirtualizedScrollbars>
<Virtuoso
totalCount={queryResult.data.length}
totalCount={messages.length}
overscan={25}
data={queryResult.data}
data={messages}
endReached={onEndReached}
itemContent={(index, message) => {
const previous = queryResult.data[index - 1];
const previous = messages[index - 1];

const newDay = isMessageNewDay(message, previous);

Expand Down Expand Up @@ -111,6 +115,11 @@ const MessageListTab = ({ iconName, title, emptyResultMessage, context, queryRes
)}
</>
)}
{isFetchingNextPage && (
<Box paddingInline={24} paddingBlock={8}>
<Throbber size='x12' />
</Box>
)}
</ContextualbarContent>
</ContextualbarDialog>
);
Expand Down
48 changes: 35 additions & 13 deletions apps/meteor/client/views/room/contextualBar/PinnedMessagesTab.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useInfiniteQuery } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import MessageListTab from './MessageListTab';
import { onClientMessageReceived } from '../../../lib/onClientMessageReceived';
import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
import { useRoom } from '../contexts/RoomContext';

const COUNT = 50;

const PinnedMessagesTab = (): ReactElement => {
const getPinnedMessages = useEndpoint('GET', '/v1/chat.getPinnedMessages');

const room = useRoom();

const pinnedMessagesQueryResult = useQuery({
const pinnedMessagesQueryResult = useInfiniteQuery({
queryKey: ['rooms', room._id, 'pinned-messages'] as const,

queryFn: async () => {
const messages: IMessage[] = [];
queryFn: async ({ pageParam }) => {
const result = await getPinnedMessages({ roomId: room._id, offset: pageParam, count: COUNT });
const processedMessages = await Promise.all(result.messages.map(mapMessageFromApi).map(onClientMessageReceived));
return {
messages: processedMessages,
total: result.total,
count: result.count,
offset: pageParam,
};
},

for (
let offset = 0, result = await getPinnedMessages({ roomId: room._id, offset: 0 });
result.count > 0;
offset += result.count, result = await getPinnedMessages({ roomId: room._id, offset })
) {
messages.push(...result.messages.map(mapMessageFromApi));
}
initialPageParam: 0,

return Promise.all(messages.map(onClientMessageReceived));
getNextPageParam: (lastPage) => {
const nextOffset = lastPage.offset + lastPage.count;
return nextOffset < lastPage.total ? nextOffset : undefined;
},
});

const messages = useMemo(
() => pinnedMessagesQueryResult.data?.pages.flatMap((page) => page.messages) ?? [],
[pinnedMessagesQueryResult.data],
);

const handleEndReached = useCallback(() => {
if (pinnedMessagesQueryResult.hasNextPage && !pinnedMessagesQueryResult.isFetching) {
pinnedMessagesQueryResult.fetchNextPage();
}
}, [pinnedMessagesQueryResult]);

const { t } = useTranslation();

return (
Expand All @@ -40,7 +58,11 @@ const PinnedMessagesTab = (): ReactElement => {
title={t('Pinned_Messages')}
emptyResultMessage={t('No_pinned_messages')}
context='pinned'
queryResult={pinnedMessagesQueryResult}
messages={messages}
isLoading={pinnedMessagesQueryResult.isLoading}
isSuccess={pinnedMessagesQueryResult.isSuccess}
isFetchingNextPage={pinnedMessagesQueryResult.isFetchingNextPage}
onEndReached={handleEndReached}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useInfiniteQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import MessageListTab from './MessageListTab';
Expand All @@ -9,28 +10,45 @@ import { roomsQueryKeys } from '../../../lib/queryKeys';
import { mapMessageFromApi } from '../../../lib/utils/mapMessageFromApi';
import { useRoom } from '../contexts/RoomContext';

const COUNT = 50;

const StarredMessagesTab = () => {
const getStarredMessages = useEndpoint('GET', '/v1/chat.getStarredMessages');

const room = useRoom();

const starredMessagesQueryResult = useQuery({
const starredMessagesQueryResult = useInfiniteQuery({
queryKey: roomsQueryKeys.starredMessages(room._id),
queryFn: async () => {
const messages: IMessage[] = [];

for (
let offset = 0, result = await getStarredMessages({ roomId: room._id, offset: 0 });
result.count > 0;
offset += result.count, result = await getStarredMessages({ roomId: room._id, offset })
) {
messages.push(...result.messages.map(mapMessageFromApi));
}

return Promise.all(messages.map(onClientMessageReceived));
queryFn: async ({ pageParam }) => {
const result = await getStarredMessages({ roomId: room._id, offset: pageParam, count: COUNT });
const processedMessages = await Promise.all(result.messages.map(mapMessageFromApi).map(onClientMessageReceived));
return {
messages: processedMessages,
total: result.total,
count: result.count,
offset: pageParam,
};
},

initialPageParam: 0,

getNextPageParam: (lastPage) => {
const nextOffset = lastPage.offset + lastPage.count;
return nextOffset < lastPage.total ? nextOffset : undefined;
},
});

const messages = useMemo(
() => starredMessagesQueryResult.data?.pages.flatMap((page) => page.messages) ?? [],
[starredMessagesQueryResult.data],
);

const handleEndReached = useCallback(() => {
if (starredMessagesQueryResult.hasNextPage && !starredMessagesQueryResult.isFetching) {
starredMessagesQueryResult.fetchNextPage();
}
}, [starredMessagesQueryResult]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const { t } = useTranslation();

return (
Expand All @@ -39,7 +57,11 @@ const StarredMessagesTab = () => {
title={t('Starred_Messages')}
emptyResultMessage={t('No_starred_messages')}
context='starred'
queryResult={starredMessagesQueryResult}
messages={messages}
isLoading={starredMessagesQueryResult.isLoading}
isSuccess={starredMessagesQueryResult.isSuccess}
isFetchingNextPage={starredMessagesQueryResult.isFetchingNextPage}
onEndReached={handleEndReached}
/>
);
};
Expand Down