sforkowany z mirror/soapbox
Refactor 'createChatMessage' mutation
rodzic
2460d85460
commit
e7106a35b3
|
@ -1,13 +1,10 @@
|
||||||
import { useMutation } from '@tanstack/react-query';
|
|
||||||
import classNames from 'clsx';
|
import classNames from 'clsx';
|
||||||
import React, { MutableRefObject, useEffect, useState } from 'react';
|
import React, { MutableRefObject, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Stack } from 'soapbox/components/ui';
|
import { Stack } from 'soapbox/components/ui';
|
||||||
// import UploadProgress from 'soapbox/components/upload-progress';
|
// import UploadProgress from 'soapbox/components/upload-progress';
|
||||||
// import UploadButton from 'soapbox/features/compose/components/upload_button';
|
// import UploadButton from 'soapbox/features/compose/components/upload_button';
|
||||||
import { useOwnAccount } from 'soapbox/hooks';
|
import { IChat, useChatActions } from 'soapbox/queries/chats';
|
||||||
import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats';
|
|
||||||
import { queryClient } from 'soapbox/queries/client';
|
|
||||||
// import { truncateFilename } from 'soapbox/utils/media';
|
// import { truncateFilename } from 'soapbox/utils/media';
|
||||||
|
|
||||||
import ChatComposer from './chat-composer';
|
import ChatComposer from './chat-composer';
|
||||||
|
@ -26,8 +23,6 @@ interface ChatInterface {
|
||||||
* Reused between floating desktop chats and fullscreen/mobile chats.
|
* Reused between floating desktop chats and fullscreen/mobile chats.
|
||||||
*/
|
*/
|
||||||
const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||||
const account = useOwnAccount();
|
|
||||||
|
|
||||||
const { createChatMessage, acceptChat } = useChatActions(chat.id);
|
const { createChatMessage, acceptChat } = useChatActions(chat.id);
|
||||||
|
|
||||||
const [content, setContent] = useState<string>('');
|
const [content, setContent] = useState<string>('');
|
||||||
|
@ -39,58 +34,20 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||||
|
|
||||||
const isSubmitDisabled = content.length === 0 && !attachment;
|
const isSubmitDisabled = content.length === 0 && !attachment;
|
||||||
|
|
||||||
const submitMessage = useMutation(({ chatId, content }: any) => createChatMessage(chatId, content), {
|
const submitMessage = () => {
|
||||||
retry: false,
|
createChatMessage.mutate({ chatId: chat.id, content }, {
|
||||||
onMutate: async (newMessage: any) => {
|
onError: (_error, _variables, context: any) => {
|
||||||
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
|
setContent(context.prevContent as string);
|
||||||
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);
|
|
||||||
setErrorSubmittingMessage(true);
|
setErrorSubmittingMessage(true);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setErrorSubmittingMessage(false);
|
setErrorSubmittingMessage(false);
|
||||||
queryClient.invalidateQueries(ChatKeys.chatMessages(chat.id));
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearState();
|
||||||
|
};
|
||||||
|
|
||||||
const clearState = () => {
|
const clearState = () => {
|
||||||
setContent('');
|
setContent('');
|
||||||
setAttachment(undefined);
|
setAttachment(undefined);
|
||||||
|
@ -101,8 +58,8 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (!isSubmitDisabled && !submitMessage.isLoading) {
|
if (!isSubmitDisabled && !createChatMessage.isLoading) {
|
||||||
submitMessage.mutate({ chatId: chat.id, content });
|
submitMessage();
|
||||||
|
|
||||||
if (!chat.accepted) {
|
if (!chat.accepted) {
|
||||||
acceptChat.mutate();
|
acceptChat.mutate();
|
||||||
|
|
|
@ -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()', () => {
|
describe('updateChat()', () => {
|
||||||
const nextUnreadCount = 5;
|
const nextUnreadCount = 5;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { getNextLink } from 'soapbox/api';
|
||||||
import compareId from 'soapbox/compare_id';
|
import compareId from 'soapbox/compare_id';
|
||||||
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
|
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
|
||||||
import { useStatContext } from 'soapbox/contexts/stat-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 { normalizeChatMessage } from 'soapbox/normalizers';
|
||||||
import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries';
|
import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries';
|
||||||
|
|
||||||
|
@ -202,8 +202,10 @@ const useChat = (chatId?: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const useChatActions = (chatId: string) => {
|
const useChatActions = (chatId: string) => {
|
||||||
|
const account = useOwnAccount();
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { setUnreadChatsCount } = useStatContext();
|
const { setUnreadChatsCount } = useStatContext();
|
||||||
|
|
||||||
const { chat, changeScreen } = useChatContext();
|
const { chat, changeScreen } = useChatContext();
|
||||||
|
@ -230,9 +232,55 @@ const useChatActions = (chatId: string) => {
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createChatMessage = (chatId: string, content: string) => {
|
const createChatMessage = useMutation(
|
||||||
return api.post<IChat>(`/api/v1/pleroma/chats/${chatId}/messages`, { content });
|
(
|
||||||
|
{ 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), {
|
const updateChat = useMutation((data: UpdateChatVariables) => api.patch<IChat>(`/api/v1/pleroma/chats/${chatId}`, data), {
|
||||||
onMutate: async (data) => {
|
onMutate: async (data) => {
|
||||||
|
|
Ładowanie…
Reference in New Issue