📌 Task TODOLIST
- [x] 불필요한 서버 요청 줄이기
- [x] TTV 줄이기
🚨 TroubleShotting
TTV를 줄이고자 초기에 가장 최신 메세지 10개만 가져오도록 했던 야무진 소망은.. 거꾸로 돌아가고 있었다.
불필요한 서버요청과 TTV를 줄이자.
Why(이유)?
- 초기에 allMsgs(최신 10개의 메세지)를 서버에서 호출 후, 가장 최상단 CSR 컴포넌트 InitChat.tsx에 props로 받아서 setQueryData()로 초기 메세지를 호출함으로써 -> 호기롭게 TTV를 줄이고자 했으나,
다른 곳에서 useMsgsQuery() (= useSuspenseQuery)를 호출하고 있어서, 아무리 최상단 컴포넌트에서 set을 한들
다른 곳에서 useSuspenseQuery로 DB에 새로운 메세지를 요청하기 때문에
화면을 focus out 했다가 돌아오면 setQueryData가 무산되고 전체 메세지가 렌더링 되는 이슈가 있었다.
- 당시에는 나름 해결해보고자,
"아 그럼 InitChat.tsx가 최상단 컴포넌트이니까 제일 먼저 useSuspenseQuery를 호출하면 다른 곳에서 호출하는 SuspenseQuery는 InitChat.tsx에서 호출한 데이터를 가져다 쓰겠구나!"
하고 InitChat.tsx에서 호출했었다.
- 그런데, 그러면 서버에서도 최근 10개를 호출해서 클라이언트에 넘기고, 클라이언트(InitChat.tsx)에서도 useMsgsQuery()를 호출해버리는 꼴로 서버에 두 번 호출하는 게 되는데 이게 무슨 뭐 피하려다가 똥을 밟은...상황이란말인가
How(과정)?
(1) 1차 시행착오
ok. 그러면 서버에서 넘겨주는 최신 10개 메세지를 캐시데이터로 쓰고, Inichat.tsx를 포함한 messages를 필요로 하는 모든 컴포넌트에서 useMsgsQuery() 대신 -> getQueryData()로 다 바꿔보자.
그런 다음, 메세지가 새로 추가되면(= payload가 추가되면)
1) invalidateQueries를 하고,
2) 그럼 DB에 데이터가 업데이트 될 테니
3) 그걸 다시 getQueryData를 한 뒤, 유저가 이전에 보던대로 렌더링 하기 위해서 가공해서 setQueryData()를 해야겠다!
또 야무진 계획을 짜본 결과,
async (payload) => {
if (payload) {
1) await queryClient.invalidateQueries({ queryKey: [MSGS_QUERY_KEY, chatRoomId] });
2) const includingNew: Message[] | undefined = queryClient.getQueryData([MSGS_QUERY_KEY, chatRoomId]);
const lastIdx =
messages?.length && includingNew?.map((i) => i.message_id).indexOf(messages[0].message_id);
messages &&
includingNew &&
3) (await queryClient.setQueryData(
[MSGS_QUERY_KEY, chatRoomId],
[...includingNew].slice(lastIdx ?? 0, includingNew.length)
));
invalidateQueries를 함에도 devTools로 확인한 캐시데이터가 업데이트 되지 않는 현상 발생..
=> 이로써 알게된 점: invalidateQueries는 useQuery/useSuspenseQuery가 없이는 캐시데이터를 무효화하고 새로운 데이터를 받아오지 못 한다..
(2) 2차 시행착오
- 그렇담 invalidateQueries를 안쓰고(useSuspenseQuery를 어떻게든 안쓰겠다는 이상한 고집...) 그냥 fetch하는 함수를 날 것으로 써볼테다!
async (payload) => {
if (payload) {
const includingNew = await fetchMsgs(chatRoomId) --> 날것의 fetch
console.log("includingNew =>", includingNew)
const lastIdx =
messages?.length && includingNew?.map((i) => i.message_id).indexOf(messages[0].message_id);
messages &&
includingNew &&
(await queryClient.setQueryData(
[MSGS_QUERY_KEY, chatRoomId],
[...includingNew].slice(lastIdx ?? 0, includingNew.length)
));
const newArr = messages &&
includingNew &&
(await queryClient.setQueryData(
[MSGS_QUERY_KEY, chatRoomId],
[...includingNew].slice(lastIdx ?? 0, includingNew.length)
));
console.log("newArr =>", newArr)
}
}
)
50을 새로 추가하면 이번에는 devTools의 캐시데이터와 setQueryData한 반환값을 콘솔로 찍어보니 둘 다 잘 업데이트 됨!
하지만, re-렌더링이 되지 않음ㅜㅜ
원인은 위에 useQuery를 사용하던 것들을 -> 모두 getQueryData()로 바꾸었다고 했는데, getQueryData()는 변경된 캐시데이터를 감지 하지 못하여 re-렌더링을 일으키지 못하기 때문이었다..
=> 이 시행착오들로 배운 점: invalidateQuries와 setQueryData, getQueryData 모두 useQuery/UseSuspenseQuery 없이 단독으로는 캐시데이터를 아예 업데이트 하지 못하거나, 업데이트 해도 re-렌더링을 일으키지 못하여 무용지물이 된다..
what(결과) ?
그러다 문득.. 예전에 prefetch로 dehydration 해서 클라이언트 컴포넌트로 넘겼던 것이 생각났다.
- 그렇지만 사실 하기 전에도 의심은 됐다.
아니 이것도 서버에서 prefetch 하지만 어쨌든 클라이언트 컴포넌트에서 useSuspenseQuery로 부르면, 두 번 호출되는 거 아니야?
- 결과는 아니었다. prefetch + dehydration 해서 클라이언트 컴포넌트의 hydration 전에 데이터를 넘긴 뒤, 컴포넌트에서 useSuspense를 해도 prefetch한 결과(최신 10개 데이터)가 DB의 전체 데이터로 갈아끼워지지 않고,
useSuspenseQuery의 반환값이 온전히 최신 10개의 데이터로 잘 반환되었기 때문이다.
하여 나의 최종 코드는..
const ChatPage = async ({ params }: { params: { chatroom_id: string } }) => {
const chatRoomId = params.chatroom_id;
const supabase = serverSupabase();
const {
data: { user }
} = await supabase.auth.getUser();
const prefetchMsgs = async () => {
const { from, to } = getFromTo(0, ITEM_INTERVAL);
const { data: allMsgs, error } = await supabase
.from('messages')
.select('*')
.eq('chatting_room_id', chatRoomId)
.range(from, to)
.order('created_at', { ascending: false });
if (error || !allMsgs) {
console.error(error.message);
} else {
return allMsgs;
}
};
const queryClient = new QueryClient();
await queryClient.prefetchQuery({ -----> (1)
queryKey: [MSGS_QUERY_KEY, chatRoomId],
queryFn: prefetchMsgs
});
return (
<main>
<HydrationBoundary state={dehydrate(queryClient)}> ----> (2)
<Suspense fallback={<ChatLoading />}>
<div className="relative flex flex-row">
<InitChat user={user} chatRoomId={chatRoomId} />
<div className="flex lg:flex-row w-full max-sm:flex-col justify-center mx-auto">
<section className="lg:flex lg:max-w-96 max-sm:absolute max-sm:z-50 max-sm:bg-white ">
<SideBar chatRoomId={chatRoomId} />
</section>
<section className="w-full max-w-xl max-h-[calc(100vh-90px)] min-h-[36rem] relative">
<div className="absolute top-0 left-0">
<SideBarButton />
</div>
<div className="h-full border rounded-md flex flex-col relative ">
<ChatHeader chatRoomId={chatRoomId} />
<Suspense>
<ChatList user={user} chatRoomId={chatRoomId} />
<ChatInput />
</Suspense>
</div>
</section>
</div>
</div>
</Suspense>
</HydrationBoundary>
</main>
);
};
chatPage > InitChat.tsx
const InitChat = ({ user, chatRoomId }: { user: User | null; chatRoomId: string }) => {
const allMsgs = useMsgsQuery(chatRoomId);
'TIL' 카테고리의 다른 글
2024.04.10 TIL #채팅 #참여중_멤버_조회 #Realtime_presence #실시간 (0) | 2024.05.07 |
---|---|
2024.04.08 TIL #user_data #전역관리 #Zustand vs #Tanstack_Query #팀회의 #기술적의사결정 (0) | 2024.05.07 |
2024.04.04 TIL #채팅 #이전메세지 _더보기 (0) | 2024.05.03 |
2024.04.03 TIL #채팅삭제 #Supabase_realtime #RLS #Delete (0) | 2024.05.02 |
2024.04.02 TIL #채팅창 만들기 #Supabse_realtime (0) | 2024.05.02 |