Skip to content

Commit b86ac68

Browse files
committed
Implemented Chat scroll Hook
1 parent ebdff63 commit b86ac68

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

components/chat/chat-messages.tsx

+32-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { Member, Message, Profile } from "@prisma/client";
44
import { ChatWelcome } from "./chat-welcome";
55
import { Loader2, ServerCrash } from "lucide-react";
66
import { useChatQuery } from "@/hooks/use-chat-query";
7-
import { Fragment } from "react";
7+
import { Fragment, useRef, ElementRef } from "react";
88
import { ChatItem } from "./chat-item";
99
import { format } from "date-fns";
1010
import { useChatSocket } from "@/hooks/use-chat-socket";
11+
import { useChatScroll } from "@/hooks/use-chat-scroll";
1112

1213
type MessageWithMemberWithProfile = Message & {
1314
member: Member & {
@@ -44,6 +45,9 @@ export const ChatMessages: React.FC<ChatMessagesProps> = ({
4445
const addKey = `chat:${chatId}:messages`;
4546
const updateKey = `chat:${chatId}:messages:update`;
4647

48+
const chatRef = useRef<ElementRef<"div">>(null);
49+
const bottomRef = useRef<ElementRef<"div">>(null);
50+
4751
const { data, status, fetchNextPage, hasNextPage, isFetchingNextPage } =
4852
useChatQuery({
4953
queryKey,
@@ -58,6 +62,14 @@ export const ChatMessages: React.FC<ChatMessagesProps> = ({
5862
updateKey,
5963
});
6064

65+
useChatScroll({
66+
chatRef,
67+
bottomRef,
68+
loadMore: fetchNextPage,
69+
shouldLoadMore: !isFetchingNextPage && !!hasNextPage,
70+
count: data?.pages[0]?.items?.length ?? 0,
71+
});
72+
6173
if (status === "loading") {
6274
return (
6375
<div className="flex flex-col flex-1 justify-center items-center">
@@ -81,9 +93,24 @@ export const ChatMessages: React.FC<ChatMessagesProps> = ({
8193
}
8294

8395
return (
84-
<div className="flex-1 flex flex-col py-4 overflow-y-auto">
85-
<div className="flex-1" />
86-
<ChatWelcome type={type} name={name} />
96+
<div ref={chatRef} className="flex-1 flex flex-col py-4 overflow-y-auto">
97+
{!hasNextPage && <div className="flex-1" />}
98+
{!hasNextPage && <ChatWelcome type={type} name={name} />}
99+
100+
{hasNextPage && (
101+
<div className="flex justify-center">
102+
{isFetchingNextPage ? (
103+
<Loader2 className="w-7 h-7 text-zinc-500 animate-spin my-4" />
104+
) : (
105+
<button
106+
onClick={() => fetchNextPage()}
107+
className="text-zinc-500 hover:text-zinc-600 dark:textzinc-400 text-xs my-4 dark:hover:text-zinc-300 transition"
108+
>
109+
Load previous messages
110+
</button>
111+
)}
112+
</div>
113+
)}
87114
<div className="flex flex-col-reverse mt-auto">
88115
{data?.pages.map((page, i) => (
89116
<Fragment key={i}>
@@ -105,6 +132,7 @@ export const ChatMessages: React.FC<ChatMessagesProps> = ({
105132
</Fragment>
106133
))}
107134
</div>
135+
<div ref={bottomRef} />
108136
</div>
109137
);
110138
};

hooks/use-chat-scroll.ts

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { useEffect, useState } from "react";
2+
3+
type ChatScrollProps = {
4+
chatRef: React.RefObject<HTMLDivElement>;
5+
bottomRef: React.RefObject<HTMLDivElement>;
6+
shouldLoadMore: boolean;
7+
loadMore: () => void;
8+
count: number;
9+
};
10+
11+
export const useChatScroll = ({
12+
chatRef,
13+
bottomRef,
14+
shouldLoadMore,
15+
loadMore,
16+
count,
17+
}: ChatScrollProps) => {
18+
const [hasInitialized, setHasInitialized] = useState(false);
19+
20+
useEffect(() => {
21+
const topDiv = chatRef?.current;
22+
23+
const handleScroll = () => {
24+
const scrollTop = topDiv?.scrollTop;
25+
26+
if (scrollTop === 0 && shouldLoadMore) {
27+
loadMore();
28+
}
29+
};
30+
31+
topDiv?.addEventListener("scroll", handleScroll);
32+
33+
return () => {
34+
topDiv?.removeEventListener("scroll", handleScroll);
35+
};
36+
}, [shouldLoadMore, loadMore, chatRef]);
37+
38+
useEffect(() => {
39+
const bottomDiv = bottomRef?.current;
40+
const topDiv = chatRef?.current;
41+
42+
const shouldAutoScroll = () => {
43+
if (!hasInitialized && bottomDiv) {
44+
setHasInitialized(true);
45+
return true;
46+
}
47+
48+
if (!topDiv) {
49+
return false;
50+
}
51+
52+
const distanceFromBottom =
53+
topDiv.scrollHeight - topDiv.scrollTop - topDiv.clientHeight;
54+
55+
return distanceFromBottom <= 100;
56+
};
57+
58+
if (shouldAutoScroll()) {
59+
setTimeout(() => {
60+
bottomRef?.current?.scrollIntoView({ behavior: "smooth" });
61+
}, 100);
62+
}
63+
}, [
64+
count,
65+
bottomRef,
66+
chatRef,
67+
hasInitialized,
68+
setHasInitialized,
69+
shouldLoadMore,
70+
]);
71+
};

0 commit comments

Comments
 (0)