Refactor 'createChatMessage' mutation

This commit is contained in:
Chewbacca 2022-11-09 12:24:44 -05:00
parent 2460d85460
commit e7106a35b3
3 changed files with 104 additions and 62 deletions

View File

@ -1,13 +1,10 @@
import { useMutation } from '@tanstack/react-query';
import classNames from 'clsx';
import React, { MutableRefObject, useEffect, useState } from 'react';
import { Stack } from 'soapbox/components/ui';
// import UploadProgress from 'soapbox/components/upload-progress';
// import UploadButton from 'soapbox/features/compose/components/upload_button';
import { useOwnAccount } from 'soapbox/hooks';
import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { IChat, useChatActions } from 'soapbox/queries/chats';
// import { truncateFilename } from 'soapbox/utils/media';
import ChatComposer from './chat-composer';
@ -26,8 +23,6 @@ interface ChatInterface {
* Reused between floating desktop chats and fullscreen/mobile chats.
*/
const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
const account = useOwnAccount();
const { createChatMessage, acceptChat } = useChatActions(chat.id);
const [content, setContent] = useState<string>('');
@ -39,58 +34,20 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
const isSubmitDisabled = content.length === 0 && !attachment;
const submitMessage = useMutation(({ chatId, content }: any) => createChatMessage(chatId, content), {
retry: false,
onMutate: async (newMessage: any) => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(['chats', 'messages', chat.id]);
// Snapshot the previous value
const prevChatMessages = queryClient.getQueryData(['chats', 'messages', chat.id]);
const prevContent = content;
// Clear state (content, attachment, etc)
clearState();
// Optimistically update to the new value
queryClient.setQueryData(['chats', 'messages', chat.id], (prevResult: any) => {
const newResult = { ...prevResult };
newResult.pages = newResult.pages.map((page: any, idx: number) => {
if (idx === 0) {
return {
...page,
result: [...page.result, {
...newMessage,
id: String(Number(new Date())),
created_at: new Date(),
account_id: account?.id,
pending: true,
unread: true,
}],
};
}
return page;
});
return newResult;
});
// Return a context object with the snapshotted value
return { prevChatMessages, prevContent };
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (_error: any, _newData: any, context: any) => {
setContent(context.prevContent);
queryClient.setQueryData(['chats', 'messages', chat.id], context.prevChatMessages);
const submitMessage = () => {
createChatMessage.mutate({ chatId: chat.id, content }, {
onError: (_error, _variables, context: any) => {
setContent(context.prevContent as string);
setErrorSubmittingMessage(true);
},
onSuccess: () => {
setErrorSubmittingMessage(false);
queryClient.invalidateQueries(ChatKeys.chatMessages(chat.id));
},
});
clearState();
};
const clearState = () => {
setContent('');
setAttachment(undefined);
@ -101,8 +58,8 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
};
const sendMessage = () => {
if (!isSubmitDisabled && !submitMessage.isLoading) {
submitMessage.mutate({ chatId: chat.id, content });
if (!isSubmitDisabled && !createChatMessage.isLoading) {
submitMessage();
if (!chat.accepted) {
acceptChat.mutate();

View File

@ -295,6 +295,43 @@ describe('useChatActions', () => {
});
});
describe('createChatMessage()', () => {
beforeEach(() => {
const initialQueryData = {
pages: [
{ result: [buildChatMessage('1')], hasMore: false, link: undefined },
],
pageParams: [undefined],
};
queryClient.setQueryData(ChatKeys.chatMessages(chat.id), initialQueryData);
__stub((mock) => {
mock
.onPost(`/api/v1/pleroma/chats/${chat.id}/messages`)
.reply(200, { hello: 'world' });
});
});
it('creates a chat message', async() => {
const { result } = renderHook(() => {
const { createChatMessage } = useChatActions(chat.id);
useEffect(() => {
createChatMessage.mutate({ chatId: chat.id, content: 'hello' });
}, []);
return createChatMessage;
});
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.data.data).toEqual({ hello: 'world' });
});
});
describe('updateChat()', () => {
const nextUnreadCount = 5;

View File

@ -8,7 +8,7 @@ import { getNextLink } from 'soapbox/api';
import compareId from 'soapbox/compare_id';
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
import { useStatContext } from 'soapbox/contexts/stat-context';
import { useApi, useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { useApi, useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
import { normalizeChatMessage } from 'soapbox/normalizers';
import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries';
@ -202,8 +202,10 @@ const useChat = (chatId?: string) => {
};
const useChatActions = (chatId: string) => {
const account = useOwnAccount();
const api = useApi();
const dispatch = useAppDispatch();
const { setUnreadChatsCount } = useStatContext();
const { chat, changeScreen } = useChatContext();
@ -230,9 +232,55 @@ const useChatActions = (chatId: string) => {
.catch(() => null);
};
const createChatMessage = (chatId: string, content: string) => {
return api.post<IChat>(`/api/v1/pleroma/chats/${chatId}/messages`, { content });
const createChatMessage = useMutation(
(
{ chatId, content }: { chatId: string, content: string },
) => api.post<IChat>(`/api/v1/pleroma/chats/${chatId}/messages`, { content }),
{
retry: false,
onMutate: async (variables) => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(['chats', 'messages', variables.chatId]);
// Snapshot the previous value
const prevContent = variables.content;
const prevChatMessages = queryClient.getQueryData(['chats', 'messages', variables.chatId]);
// Optimistically update to the new value
queryClient.setQueryData(ChatKeys.chatMessages(variables.chatId), (prevResult: any) => {
const newResult = { ...prevResult };
newResult.pages = newResult.pages.map((page: any, idx: number) => {
if (idx === 0) {
return {
...page,
result: [...page.result, {
content: variables.content,
id: String(Number(new Date())),
created_at: new Date(),
account_id: account?.id,
pending: true,
unread: true,
}],
};
}
return page;
});
return newResult;
});
return { prevChatMessages, prevContent };
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (_error: any, variables, context: any) => {
queryClient.setQueryData(['chats', 'messages', variables.chatId], context.prevChatMessages);
},
onSuccess: (_data: any, variables) => {
queryClient.invalidateQueries(ChatKeys.chatMessages(variables.chatId));
},
},
);
const updateChat = useMutation((data: UpdateChatVariables) => api.patch<IChat>(`/api/v1/pleroma/chats/${chatId}`, data), {
onMutate: async (data) => {