Reputation: 1
The getChatMsg function is an API that brings previous chat history.
Even though it was invalidated with invalidateQueries, the chat history is not reflected in real time.
Even if you manage input with useState, the fact that it is not invalidated does not change.
import React, { useEffect, useMemo, useRef, useState } from 'react'
import SockJS from 'sockjs-client/dist/sockjs'
import { Stomp, type CompatClient } from '@stomp/stompjs'
import { useParams } from 'react-router-dom'
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'
import { jwtDecode } from 'jwt-decode'
import { throttle } from 'lodash'
import DetailHeader from '@/components/DetailHeader/DetailHeader'
import {
BubbleBox,
BubbleContainer,
ChatBubble,
ChatContainer,
MessageContainer,
MsgInputBox,
SendButton,
SendTime,
Username,
} from './styles'
import { getLocalStorageItem } from '@/util/localStorage'
import { getChatMsg } from '@/apis/meeting'
import { type ChatMessage } from '@/type/chat'
import LoadingPage from '@/shared/LoadingPage'
import ErrorPage from '@/shared/ErrorPage'
function Chat(): JSX.Element {
const { meetingId } = useParams()
const queryClient = useQueryClient()
const stompClient = useRef<CompatClient | null>(null)
const test = useRef<HTMLInputElement | null>(null)
const [prevScrollHeight, setPrevScrollHeight] = useState<number | null>(null)
const socket = new SockJS(`${import.meta.env.VITE_SOCKET_URL}`)
const token: string = getLocalStorageItem('accessToken')
const headers = { Authorization: `Bearer ${token}` }
const decodedToken = jwtDecode(token)
//= ======================================================================
const scrollBoxRef = useRef<HTMLDivElement>(null)
const { data, fetchNextPage, isLoading, isError } = useInfiniteQuery({
queryKey: ['getAllChatMessages'],
queryFn: async ({ pageParam }) => {
return await getChatMsg({ meetingId: Number(meetingId), pageParam })
},
getNextPageParam: (lastPage) => {
if (!lastPage.isLast) return lastPage.nextPage
return undefined
},
initialPageParam: 1,
})
const handleFetchPages = (): void => {
setPrevScrollHeight(scrollBoxRef.current?.scrollHeight ?? null)
void fetchNextPage()
}
const handleScroll: () => void = throttle(() => {
if (scrollBoxRef?.current === null) return
const scrollBox = scrollBoxRef.current
if (scrollBox.scrollTop === 0) {
handleFetchPages()
}
}, 500)
const chatDatas = useMemo(() => {
let list: ChatMessage[] = []
data != null &&
data.pages.forEach(({ result }) => (list = [...list, ...result]))
return list.reverse()
}, [data])
useEffect(() => {
const scrollBox = scrollBoxRef.current
scrollBox?.addEventListener('scroll', handleScroll)
return () => {
scrollBox?.removeEventListener('scroll', handleScroll)
}
}, [handleScroll])
//= ======================================================================
useEffect(() => {
stompClient.current = Stomp.over(socket)
stompClient.current?.connect(headers, (): void => {
stompClient.current?.subscribe(`/topic/rooms/${meetingId}/chat`, () => {})
})
return () => {
if (stompClient.current !== null) {
stompClient.current?.disconnect()
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [meetingId])
const sendChatMessage = (): void => {
const chatRequest = { content: test.current?.value }
if (test.current?.value === '') return
stompClient.current?.send(
`/app/api/meetings/${meetingId}/chat`,
headers,
JSON.stringify(chatRequest)
)
if (test.current !== null) {
test.current.value = ''
}
void queryClient.invalidateQueries({ queryKey: ['getAllChatMessages'] })
}
const handleKeyEnter = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (e.key === 'Enter') {
sendChatMessage()
}
}
const scrollToBottom = (): void => {
const messageContainer = document.getElementById('messageContainer')
if (messageContainer !== null) {
messageContainer.scrollTop = messageContainer.scrollHeight
}
}
useEffect(() => {
scrollToBottom()
}, [data])
useEffect(() => {
if (prevScrollHeight !== null && scrollBoxRef.current !== null) {
scrollBoxRef.current.scrollTop =
scrollBoxRef.current.scrollHeight - prevScrollHeight
setPrevScrollHeight(null)
} else {
scrollToBottom()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chatDatas])
if (isLoading) return <LoadingPage name="페이지를" />
if (isError) return <ErrorPage />
return (
<ChatContainer>
<DetailHeader meetingId={Number(meetingId)} />
<MessageContainer id="messageContainer" ref={scrollBoxRef}>
<BubbleContainer>
{chatDatas.map((e: ChatMessage) => (
<BubbleBox
key={e.chatId}
$isMe={e.sender.memberEmail === decodedToken.sub}
>
<Username $isMe={e.sender.memberEmail === decodedToken.sub}>
{e.sender.memberName}
</Username>
<div className="msg">
<ChatBubble $isMe={e.sender.memberEmail === decodedToken.sub}>
{e.content}
</ChatBubble>
<SendTime>{e.createdAt.slice(11, 16)}</SendTime>
</div>
</BubbleBox>
))}
</BubbleContainer>
</MessageContainer>
<MsgInputBox>
<input
type="text"
ref={test}
onKeyUp={(e) => {
handleKeyEnter(e)
}}
placeholder="메세지를 입력해 주세요"
/>
<SendButton onClick={sendChatMessage}>
<img src="/assets/send.svg" alt="" />
</SendButton>
</MsgInputBox>
</ChatContainer>
)
}
export default Chat
When the sendChatMessage function is executed, it is reflected on my page, but why is it not reflected in real time on other pages? You must refresh to view real-time details. In the network tab, pub/sub is working well, but it is not rendering on the screen right away. What should I do?
Upvotes: 0
Views: 43