Re-sort the ChatList when new messages come in
This commit is contained in:
parent
15c22863d7
commit
93a696a72f
4 changed files with 115 additions and 8 deletions
|
@ -5,7 +5,7 @@ import messages from 'soapbox/locales/messages';
|
|||
import { normalizeChatMessage } from 'soapbox/normalizers';
|
||||
import { ChatKeys, IChat, isLastMessage } from 'soapbox/queries/chats';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
import { updatePageItem, appendPageItem, removePageItem, flattenPages, PaginatedResult } from 'soapbox/utils/queries';
|
||||
import { updatePageItem, appendPageItem, removePageItem, flattenPages, PaginatedResult, sortQueryData } from 'soapbox/utils/queries';
|
||||
import { play, soundCache } from 'soapbox/utils/sounds';
|
||||
|
||||
import { connectStream } from '../stream';
|
||||
|
@ -56,17 +56,28 @@ interface ChatPayload extends Omit<Chat, 'last_message'> {
|
|||
last_message: ChatMessage | null,
|
||||
}
|
||||
|
||||
const dateComparator = (chatA: IChat, chatB: IChat): number => {
|
||||
const chatADate = new Date(chatA.last_message?.created_at as string);
|
||||
const chatBDate = new Date(chatB.last_message?.created_at as string);
|
||||
|
||||
if (chatBDate < chatADate) return -1;
|
||||
if (chatBDate > chatADate) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
const updateChat = (payload: ChatPayload) => {
|
||||
const { id: chatId, last_message: lastMessage } = payload;
|
||||
|
||||
const currentChats = flattenPages(queryClient.getQueryData<InfiniteData<PaginatedResult<unknown>>>(ChatKeys.chatSearch()));
|
||||
|
||||
// Update the specific Chat query data.
|
||||
// queryClient.setQueryData<Chat>(ChatKeys.chat(chatId), payload as any);
|
||||
const currentChats = flattenPages(
|
||||
queryClient.getQueryData<InfiniteData<PaginatedResult<IChat>>>(ChatKeys.chatSearch()),
|
||||
);
|
||||
|
||||
if (currentChats?.find((chat: any) => chat.id === chatId)) {
|
||||
// If the chat exists in the client, let's update it.
|
||||
updatePageItem<Chat>(ChatKeys.chatSearch(), payload as any, (o, n) => o.id === n.id);
|
||||
// Now that we have the new chat loaded, let's re-sort to put
|
||||
// the most recent on top.
|
||||
sortQueryData<IChat>(ChatKeys.chatSearch(), dateComparator);
|
||||
} else {
|
||||
// If this is a brand-new chat, let's invalid the queries.
|
||||
queryClient.invalidateQueries(ChatKeys.chatSearch());
|
||||
|
|
|
@ -74,7 +74,7 @@ const ChatKeys = {
|
|||
/** Check if item is most recent */
|
||||
const isLastMessage = (chatMessageId: string): boolean => {
|
||||
const queryData = queryClient.getQueryData<InfiniteData<PaginatedResult<IChat>>>(ChatKeys.chatSearch());
|
||||
const items = flattenPages(queryData);
|
||||
const items = flattenPages<IChat>(queryData);
|
||||
const chat = items?.find((item) => item.last_message?.id === chatMessageId);
|
||||
|
||||
return !!chat;
|
||||
|
|
63
app/soapbox/utils/__tests__/queries.test.ts
Normal file
63
app/soapbox/utils/__tests__/queries.test.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { InfiniteData } from '@tanstack/react-query';
|
||||
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
|
||||
import { PaginatedResult, sortQueryData } from '../queries';
|
||||
|
||||
interface Item {
|
||||
id: number
|
||||
}
|
||||
|
||||
const buildItem = (id: number): Item => ({ id });
|
||||
|
||||
const queryKey = ['test', 'query'];
|
||||
|
||||
describe('sortQueryData()', () => {
|
||||
describe('without cached data', () => {
|
||||
it('safely returns undefined', () => {
|
||||
sortQueryData<Item>(queryKey, (a, b) => b.id - a.id);
|
||||
const nextQueryData = queryClient.getQueryData<InfiniteData<PaginatedResult<Item>>>(queryKey);
|
||||
expect(nextQueryData).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with cached data', () => {
|
||||
const cachedQueryData = {
|
||||
pages: [
|
||||
{
|
||||
result: [...Array(20).fill(0).map((_, idx) => buildItem(idx))],
|
||||
hasMore: false,
|
||||
link: undefined,
|
||||
},
|
||||
{
|
||||
result: [...Array(4).fill(0).map((_, idx) => buildItem(idx + 20))],
|
||||
hasMore: true,
|
||||
link: 'my-link',
|
||||
},
|
||||
],
|
||||
pageParams: [undefined],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient.setQueryData(queryKey, cachedQueryData);
|
||||
});
|
||||
|
||||
it('sorts the cached data', () => {
|
||||
const initialQueryData = queryClient.getQueryData<InfiniteData<PaginatedResult<Item>>>(queryKey);
|
||||
expect(initialQueryData?.pages[0].result[0].id === 0); // first id is 0
|
||||
sortQueryData<Item>(queryKey, (a, b) => b.id - a.id); // sort descending
|
||||
const nextQueryData = queryClient.getQueryData<InfiniteData<PaginatedResult<Item>>>(queryKey);
|
||||
expect(nextQueryData?.pages[0].result[0].id === 0); // first id is now 23
|
||||
});
|
||||
|
||||
it('persists the metadata', () => {
|
||||
const initialQueryData = queryClient.getQueryData<InfiniteData<PaginatedResult<Item>>>(queryKey);
|
||||
const initialMetaData = initialQueryData?.pages.map((page) => page.link);
|
||||
sortQueryData<Item>(queryKey, (a, b) => b.id - a.id);
|
||||
const nextQueryData = queryClient.getQueryData<InfiniteData<PaginatedResult<Item>>>(queryKey);
|
||||
const nextMetaData = nextQueryData?.pages.map((page) => page.link);
|
||||
|
||||
expect(initialMetaData).toEqual(nextMetaData);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import { queryClient } from 'soapbox/queries/client';
|
||||
|
||||
import type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';
|
||||
import type { InfiniteData, QueryKey } from '@tanstack/react-query';
|
||||
|
||||
export interface PaginatedResult<T> {
|
||||
result: T[],
|
||||
|
@ -9,7 +9,7 @@ export interface PaginatedResult<T> {
|
|||
}
|
||||
|
||||
/** Flatten paginated results into a single array. */
|
||||
const flattenPages = <T>(queryData: UseInfiniteQueryResult<PaginatedResult<T>>['data']) => {
|
||||
const flattenPages = <T>(queryData: InfiniteData<PaginatedResult<T>> | undefined) => {
|
||||
return queryData?.pages.reduce<T[]>(
|
||||
(prev: T[], curr) => [...curr.result, ...prev],
|
||||
[],
|
||||
|
@ -53,9 +53,42 @@ const removePageItem = <T>(queryKey: QueryKey, itemToRemove: T, isItem: (item: T
|
|||
});
|
||||
};
|
||||
|
||||
const paginateQueryData = <T>(array: T[] | undefined) => {
|
||||
return array?.reduce((resultArray: any, item: any, index: any) => {
|
||||
const chunkIndex = Math.floor(index / 20);
|
||||
|
||||
if (!resultArray[chunkIndex]) {
|
||||
resultArray[chunkIndex] = []; // start a new chunk
|
||||
}
|
||||
|
||||
resultArray[chunkIndex].push(item);
|
||||
|
||||
return resultArray;
|
||||
}, []);
|
||||
};
|
||||
|
||||
const sortQueryData = <T>(queryKey: QueryKey, comparator: (a: T, b: T) => number) => {
|
||||
queryClient.setQueryData<InfiniteData<PaginatedResult<T>>>(queryKey, (prevResult) => {
|
||||
if (prevResult) {
|
||||
const nextResult = { ...prevResult };
|
||||
const flattenedQueryData = flattenPages(nextResult);
|
||||
const sortedQueryData = flattenedQueryData?.sort(comparator);
|
||||
const paginatedPages = paginateQueryData(sortedQueryData);
|
||||
const newPages = paginatedPages.map((page: T, idx: number) => ({
|
||||
...prevResult.pages[idx],
|
||||
result: page,
|
||||
}));
|
||||
|
||||
nextResult.pages = newPages;
|
||||
return nextResult;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
flattenPages,
|
||||
updatePageItem,
|
||||
appendPageItem,
|
||||
removePageItem,
|
||||
sortQueryData,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue