Re-sort the ChatList when new messages come in

This commit is contained in:
Chewbacca 2022-11-10 15:42:59 -05:00
parent 15c22863d7
commit 93a696a72f
4 changed files with 115 additions and 8 deletions

View file

@ -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());

View file

@ -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;

View 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);
});
});
});

View file

@ -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,
};