Skip to content

Commit f1c124b

Browse files
committed
Minimize flickering
1 parent f507e1d commit f1c124b

File tree

1 file changed

+48
-47
lines changed

1 file changed

+48
-47
lines changed

chat/src/components/MessageList.tsx

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use client";
22

3+
import { useLayoutEffect, useRef } from "react";
4+
35
interface Message {
46
role: string;
57
content: string;
@@ -17,6 +19,28 @@ interface MessageListProps {
1719
}
1820

1921
export default function MessageList({ messages }: MessageListProps) {
22+
const scrollAreaRef = useRef<HTMLDivElement>(null);
23+
// Avoid the message list to change its height all the time. It causes some
24+
// flickering in the screen because some messages, as the ones displaying
25+
// progress statuses, are changing the content(the number of lines) and size
26+
// constantily. To minimize it, we keep track of the biggest scroll height of
27+
// the content, and use that as the min height of the scroll area.
28+
const contentMinHeight = useRef(0);
29+
30+
useLayoutEffect(() => {
31+
if (
32+
scrollAreaRef.current &&
33+
scrollAreaRef.current.scrollHeight > contentMinHeight.current
34+
) {
35+
const isFirstScroll = contentMinHeight.current === 0;
36+
scrollAreaRef.current.scrollTo({
37+
top: scrollAreaRef.current.scrollHeight,
38+
behavior: isFirstScroll ? "instant" : "smooth",
39+
});
40+
contentMinHeight.current = scrollAreaRef.current.scrollHeight;
41+
}
42+
}, [messages]);
43+
2044
// If no messages, show a placeholder
2145
if (messages.length === 0) {
2246
return (
@@ -26,53 +50,12 @@ export default function MessageList({ messages }: MessageListProps) {
2650
);
2751
}
2852

29-
// Define a component for the animated dots
30-
const LoadingDots = () => (
31-
<div className="flex space-x-1">
32-
<div
33-
aria-hidden="true"
34-
className={`size-2 rounded-full bg-foreground animate-pulse [animation-delay:0ms]`}
35-
/>
36-
<div
37-
aria-hidden="true"
38-
className={`size-2 rounded-full bg-foreground animate-pulse [animation-delay:300ms]`}
39-
/>
40-
<div
41-
aria-hidden="true"
42-
className={`size-2 rounded-full bg-foreground animate-pulse [animation-delay:600ms]`}
43-
/>
44-
<span className="sr-only">Loading...</span>
45-
</div>
46-
);
47-
4853
return (
49-
<div
50-
className="overflow-y-auto flex-1"
51-
ref={(scrollAreaRef) => {
52-
if (!scrollAreaRef) {
53-
return;
54-
}
55-
56-
scrollAreaRef.scrollTo({
57-
top: scrollAreaRef.scrollHeight,
58-
});
59-
60-
const callback: MutationCallback = (mutationsList) => {
61-
for (const mutation of mutationsList) {
62-
if (mutation.type === "childList") {
63-
scrollAreaRef.scrollTo({
64-
top: scrollAreaRef.scrollHeight,
65-
behavior: "smooth",
66-
});
67-
}
68-
}
69-
};
70-
71-
const observer = new MutationObserver(callback);
72-
observer.observe(scrollAreaRef, { childList: true, subtree: false });
73-
}}
74-
>
75-
<div className="p-4 flex flex-col gap-4 max-w-4xl mx-auto">
54+
<div className="overflow-y-auto flex-1" ref={scrollAreaRef}>
55+
<div
56+
className="p-4 flex flex-col gap-4 max-w-4xl mx-auto"
57+
style={{ minHeight: contentMinHeight.current }}
58+
>
7659
{messages.map((message) => (
7760
<div
7861
key={message.id ?? "draft"}
@@ -93,7 +76,7 @@ export default function MessageList({ messages }: MessageListProps) {
9376
{message.role !== "user" && message.content === "" ? (
9477
<LoadingDots />
9578
) : (
96-
message.content
79+
message.content.trim()
9780
)}
9881
</div>
9982
</div>
@@ -103,3 +86,21 @@ export default function MessageList({ messages }: MessageListProps) {
10386
</div>
10487
);
10588
}
89+
90+
const LoadingDots = () => (
91+
<div className="flex space-x-1">
92+
<div
93+
aria-hidden="true"
94+
className={`size-2 rounded-full bg-foreground animate-pulse [animation-delay:0ms]`}
95+
/>
96+
<div
97+
aria-hidden="true"
98+
className={`size-2 rounded-full bg-foreground animate-pulse [animation-delay:300ms]`}
99+
/>
100+
<div
101+
aria-hidden="true"
102+
className={`size-2 rounded-full bg-foreground animate-pulse [animation-delay:600ms]`}
103+
/>
104+
<span className="sr-only">Loading...</span>
105+
</div>
106+
);

0 commit comments

Comments
 (0)