Skip to content

Commit a0e538f

Browse files
committed
fix: autoscroll
1 parent ac21d13 commit a0e538f

File tree

1 file changed

+19
-17
lines changed

1 file changed

+19
-17
lines changed

chat/src/components/message-list.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import React, {useLayoutEffect, useRef, useEffect, useCallback, useMemo} from "react";
3+
import React, {useLayoutEffect, useRef, useEffect, useCallback, useMemo, useState} from "react";
44

55
interface Message {
66
role: string;
@@ -24,18 +24,20 @@ interface ProcessedMessageProps {
2424
}
2525

2626
export default function MessageList({messages}: MessageListProps) {
27-
const scrollAreaRef = useRef<HTMLDivElement>(null);
27+
const [scrollAreaRef, setScrollAreaRef] = useState<HTMLDivElement | null>(null);
2828

2929
// Track if user is at bottom - default to true for initial scroll
3030
const isAtBottomRef = useRef(true);
3131
// Track the last known scroll height to detect new content
3232
const lastScrollHeightRef = useRef(0);
33+
// Track if we're currently doing a programmatic scroll
34+
const isProgrammaticScrollRef = useRef(false);
3335

3436
const checkIfAtBottom = useCallback(() => {
35-
if (!scrollAreaRef.current) return false;
36-
const { scrollTop, scrollHeight, clientHeight } = scrollAreaRef.current;
37+
if (!scrollAreaRef) return false;
38+
const { scrollTop, scrollHeight, clientHeight } = scrollAreaRef;
3739
return scrollTop + clientHeight >= scrollHeight - 10; // 10px tolerance
38-
}, []);
40+
}, [scrollAreaRef]);
3941

4042
// Track Ctrl (Windows/Linux) or Cmd (Mac) key state
4143
// This is so that underline is only visible when hover + cmd/ctrl
@@ -60,26 +62,26 @@ export default function MessageList({messages}: MessageListProps) {
6062

6163
// Update isAtBottom on scroll
6264
useEffect(() => {
63-
const scrollContainer = scrollAreaRef.current;
64-
if (!scrollContainer) return;
65+
if (!scrollAreaRef) return;
6566

6667
const handleScroll = () => {
68+
if (isProgrammaticScrollRef.current) return;
6769
isAtBottomRef.current = checkIfAtBottom();
6870
};
6971

7072
// Initial check
7173
handleScroll();
7274

73-
scrollContainer.addEventListener("scroll", handleScroll);
74-
return () => scrollContainer.removeEventListener("scroll", handleScroll);
75-
}, [checkIfAtBottom]);
75+
scrollAreaRef.addEventListener("scroll", handleScroll);
76+
scrollAreaRef.addEventListener("scrollend", () => isProgrammaticScrollRef.current = false);
77+
return () => scrollAreaRef.removeEventListener("scroll", handleScroll);
78+
}, [checkIfAtBottom, scrollAreaRef]);
7679

7780
// Handle auto-scrolling when messages change
7881
useLayoutEffect(() => {
79-
if (!scrollAreaRef.current) return;
82+
if (!scrollAreaRef) return;
8083

81-
const scrollContainer = scrollAreaRef.current;
82-
const currentScrollHeight = scrollContainer.scrollHeight;
84+
const currentScrollHeight = scrollAreaRef.scrollHeight;
8385

8486
// Check if this is new content (scroll height increased)
8587
const hasNewContent = currentScrollHeight > lastScrollHeightRef.current;
@@ -95,7 +97,8 @@ export default function MessageList({messages}: MessageListProps) {
9597
hasNewContent &&
9698
(isFirstRender || isAtBottomRef.current || isNewUserMessage)
9799
) {
98-
scrollContainer.scrollTo({
100+
isProgrammaticScrollRef.current = true;
101+
scrollAreaRef.scrollTo({
99102
top: currentScrollHeight,
100103
behavior: isFirstRender ? "instant" : "smooth",
101104
});
@@ -105,7 +108,7 @@ export default function MessageList({messages}: MessageListProps) {
105108

106109
// Update the last known scroll height
107110
lastScrollHeightRef.current = currentScrollHeight;
108-
}, [messages]);
111+
}, [messages, scrollAreaRef]);
109112

110113
// If no messages, show a placeholder
111114
if (messages.length === 0) {
@@ -117,7 +120,7 @@ export default function MessageList({messages}: MessageListProps) {
117120
}
118121

119122
return (
120-
<div className="overflow-y-auto flex-1" ref={scrollAreaRef}>
123+
<div className="overflow-y-auto flex-1" ref={setScrollAreaRef}>
121124
<div
122125
className="p-4 flex flex-col gap-4 max-w-4xl mx-auto transition-all duration-300 ease-in-out min-h-0">
123126
{messages.map((message, index) => (
@@ -191,7 +194,6 @@ const ProcessedMessage = React.memo(function ProcessedMessage({
191194

192195
const linkedContent = useMemo(() => {
193196
return messageContent.split(urlRegex).map((content, idx) => {
194-
console.log(content)
195197
if (urlRegex.test(content)) {
196198
return (
197199
<a

0 commit comments

Comments
 (0)