문제 상황
채팅방 첫 입장 시 가장 최신의 메시지만 보이고 더 이전의 메시지는 무한스크롤을 통해 로드해야 하는데, 이전 메시지가 모두 한번에 로드됨
원인
•
useIntersectionObserver의 ref 요소 위치
◦
ref 요소가 메시지 목록 아래에 있어, 채팅방에 처음 들어오면 바로 ref 요소가 뷰포트에 들어오게 됨
그 즉시 모든 페이지가 연속으로 로드됨
•
잘못된 이전 메시지 로드 및 스크롤 관리
◦
이전 메시지 로드와 스크롤 위치 조정을 하나의 함수에서 동시에 처리
DOM 업데이트가 완료되기 전에 스크롤 위치가 조정될 수 있음
해결
useIntersectionObserver의 ref 요소 위치 변경
•
ref 요소를 메시지 목록 위에 위치하도록 수정하여, 사용자가 스크롤을 위로 올려서 이전 메시지를 보고자 할 때 이전 메시지가 로드됨
return (
<div ref={chatContainerRef} className="flex-1 h-[500px] overflow-y-auto space-y-4 px-6 pb-6">
{hasNextPage && !isFetchingNextPage && (
<div ref={ref} className="h-1" /> // ref 요소가 메시지들 위에 위치
)}
{messages.map((msg, idx) => {
// ... 메시지 렌더링
})}
</div>
);
TypeScript
복사
이전 메시지 로드 함수와 (초기 로딩, 이전 메시지 로드, 새 메시지 추가) 3가지의 상황을 분리하여 스크롤 처리
•
이전 메시지 로드 함수
const handleFetchPreviousMessages = async () => {
const previousScrollHeight = chatContainerRef.current.scrollHeight;
const previousScrollTop = chatContainerRef.current.scrollTop;
await fetchNextPage();
chatContainerRef.current.scrollTop = newScrollHeight - previousScrollHeight + previousScrollTop;
};
TypeScript
복사
•
스크롤 처리
◦
초기 로딩 시: 스크롤을 맨 아래로 이동
useEffect(() => {
if (messages.length && chatContainerRef.current && isInitialLoad.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
lastMessageLength.current = messages.length;
isInitialLoad.current = false;
}
}, [messages]);
TypeScript
복사
◦
이전 메시지 로드 시: DOM 업데이트 후 스크롤 위치 조정 / 이전 메시지 개수 기준으로 적절한 스크롤 위치 계산
useEffect(() => {
if (!isInitialLoad.current && isLoadingPrevMessages.current &&
chatContainerRef.current && messages.length !== lastMessageLength.current) {
const newScrollHeight = chatContainerRef.current.scrollHeight;
const targetScrollTop = newScrollHeight - lastMessageLength.current * 100;
chatContainerRef.current.scrollTop = targetScrollTop;
lastMessageLength.current = messages.length;
isLoadingPrevMessages.current = false;
}
}, [messages]);
TypeScript
복사
◦
새 메시지 추가 시: 스크롤을 맨 아래로 이동 / isLoadingPrevMessages 체크를 통해 이전 메시지 로드와 구분
useEffect(() => {
if (!isInitialLoad.current && !isLoadingPrevMessages.current &&
chatContainerRef.current && messages.length > lastMessageLength.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
lastMessageLength.current = messages.length;
}
}, [messages]);
TypeScript
복사