작성
·
419
0
import Chat from '@components/Chat';
import { ChatZone, Section, StickyHeader } from '@components/ChatList/style';
import { IChat, IDM } from '@typings/db';
import React, { FC, RefObject, VFC, forwardRef, useCallback, useRef } from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
interface Props {
scrollRef: RefObject<Scrollbars>;
chatSections: { [key: string]: IDM[] };
setSize: (f: (index: number) => number) => Promise<IDM[][] | undefined>;
isEmpty: boolean;
isReachingEnd: boolean;
}
const ChatList: VFC<Props> = (({ chatSections, setSize, isEmpty, scrollRef, isReachingEnd}) => {
const onScroll = useCallback((values) => {
// 끝에 도달하면 불러오지 않기
if(values.scrollTop === 0 && !isReachingEnd){
console.log('가장 위');
setSize((prevSize) => prevSize + 1).then(()=>{
// 스크롤 위치 유지
if(scrollRef?.current){
scrollRef.current?.scrollTop(scrollRef.current?.getScrollHeight() - values.scrollHeight);
}
});
}
}, []);
return (
<ChatZone>
<Scrollbars autoHide ref={scrollRef} onScrollFrame={onScroll}>
{Object.entries(chatSections).map(([date, chats]) => {
return (
<Section className={`section-${date}`} key={date}>
<StickyHeader>
<button>{date}</button>
</StickyHeader>
{chats.map((chat) => (
<Chat key={chat.id} data={chat} />
))}
</Section>
);
})}
</Scrollbars>
</ChatZone>
);
});
export default ChatList;
이쪽 코드는 문제가 없는것같은데 희한하게 위치가 유지가 되지않고 원래처럼 쭉 올라가버립니다.. ref쪽이 문제인가요..?
혹시몰라 DirectMessage 컴포넌트도 아래에 첨부하겠습니다.
import React, { useCallback, useEffect, useRef } from 'react';
import gravator from 'gravatar';
import useSWR, { mutate } from 'swr';
// swr 인피니티스크롤링 전용 메서드
import useSWRInfinite from 'swr/infinite';
import { IDM, IUser } from '@typings/db';
import fetcher from '@utils/fetcher';
import { useParams } from 'react-router';
import ChatBox from '@components/ChatBox';
import { Container, Header } from '@pages/DirectMessage/style';
import ChatList from '@components/ChatList';
import useInput from '@hooks/useInput';
import axios from 'axios';
import makeSection from '@utils/makeSection';
import Scrollbars from 'react-custom-scrollbars';
const DirectMessage = () => {
const { workspace, id } = useParams<{ workspace: string; id: string }>();
const { data: userData } = useSWR(`http://localhost:3095/api/workspaces/${workspace}/users/${id}`, fetcher);
// 내정보
const { data: myData } = useSWR(`http://localhost:3095/api/users`, fetcher);
const [chat, onChangeChat, setChat] = useInput('');
// 과거 채팅리스트에서 채팅을 치면 최신목록으로 바로 스크롤을 내려줄려면 ref를
// 이 컴포넌트에서 props로 내려줘야하기 때문에 forwardRef를 사용해서 props로 넘겨준다
// 💡 HTML 엘리먼트가 아닌 React 컴포넌트에서 ref prop을 사용하려면 React에서 제공하는 forwardRef()라는 함수를 사용해야 합니다
const scrollbarRef = useRef<Scrollbars>(null);
// 채팅 받아오는곳 (setSize : 페이지수를 바꿔줌)
// useSWRInfinite를 쓰면 [{id:1},{id:2},{id:3},{id:4}] 1차원배열이 [[{id:1},{id:2}],[{id:3},{id:4}]] 2차원배열이 된다.
const {
data: chatData,
mutate: mutateChat,
setSize,
} = useSWRInfinite<IDM[]>(
(index) => `http://localhost:3095/api/workspaces/${workspace}/dms/${id}/chats?perPage=20&page=${index + 1}`,
fetcher,
);
// 데이터 40 개중에 20개씩 사져오면 첫번째페이지부터 20 + 20 + 0 세번째 페이지 0 이되면 isEmpty, isReachingEnd는 true가 됨
// 반대의 상황에서 데이터가 45개면 20 + 20 + 5 isEmpty는 0이 아니라서 false isReachingEnd는 여전히 데이터 가져옴
const isEmpty = chatData?.[0]?.length === 0;
const isReachingEnd = isEmpty || (chatData && chatData[chatData.length - 1]?.length < 20) || false;
const onSubmitForm = useCallback(
(e) => {
e.preventDefault();
if (chat?.trim() && chatData) {
const savedChat = chat;
// 💡 옵티미스틱 UI
// 서버쪽에 다녀오지 않아도 성공해서 데이터가 있는거처럼 보이게 미리 만듦
mutateChat((prevChatData) => {
// infinite 스크롤링은 2차원 배열이다.
prevChatData?.[0].unshift({ // unshift : 앞쪽에 추가
id: (chatData[0][0]?.id || 0) + 1,
content: savedChat,
SenderId: myData.id,
Sender: myData,
ReceiverId: userData.id,
Receiver: userData,
createdAt : new Date(),
});
return prevChatData;
},false) // 옵티미스틱 UI 할땐 이부분이 항상 false
.then(()=>{
setChat(''); // 버튼클릭 시 기존 채팅지우기
scrollbarRef.current?.scrollToBottom(); // 채팅 첬을때 맨 아래로
})
axios
.post(
`http://localhost:3095/api/workspaces/${workspace}/dms/${id}/chats`,
{
content: chat,
},
{
withCredentials: true,
},
)
.then(() => {
mutateChat(); // SWR에서 데이터를 다시 불러와서 캐시를 갱신하는 역할을 합니다.
})
.catch(() => {
console.error;
});
}
},
[chat, chatData, myData, userData, workspace, id],
);
// (채팅이 최신것을 아래에 두기 위함) = 기존것 데이터를두고 새 데이터를 뒤집어서 출력 / flat() 배열을 1차원 배열로 만들어줌
const chatSections = makeSection(chatData ? [...chatData].flat().reverse() : []);
// 로딩 시 스크롤바 제일 아래로
useEffect(()=>{
if(chatData?.length === 1){ // 채팅 데이터가 있어서 불러온 경우
scrollbarRef.current?.scrollToBottom(); // 가장 아래쪽으로 내려줌
}
},[chatData])
// 로딩
if (!userData || !myData) {
return null;
}
return (
<Container>
<Header>
<img src={gravator.url(userData.email, { s: '24px', d: 'retro' })} alt={userData.nickname}></img>
<span>{userData.nickname}</span>
</Header>
{/* 컴포넌트 위치를 미리 지정해도 좋다. */}
{/* 전역 상태관리 라이브러리를 사용해도 컴포넌트상황에따라 props 로 넘겨줌*/}
<ChatList
scrollRef={scrollbarRef}
chatSections={chatSections}
setSize={setSize}
isEmpty={isEmpty}
isReachingEnd={isReachingEnd}
/>
<ChatBox chat={chat} onChangeChat={onChangeChat} onSubmitForm={onSubmitForm} />
</Container>
);
};
export default DirectMessage;
아직도 해결법을 못찾았는데 혹시 맥 환경이랑 상관이 있나요??