refactor: Consolidate Message Scrolling & other Logic to Custom Hooks 🔄 (#1257)

* refactor: remove unnecessary drilling/invoking of ScrollToBottom
- feat: useMessageScrolling: consolidates all scrolling logic to hook
- feat: useMessageHelpers: creates message utilities and consolidates logic from UI component

* fix: ensure automatic scrolling is triggered by messagesTree re-render and is throttled
This commit is contained in:
Danny Avila 2023-12-01 19:54:09 -05:00 committed by GitHub
parent ebd23f7295
commit 4674a54c70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 208 additions and 169 deletions

View file

@ -1,10 +1,9 @@
import { useLayoutEffect, useState, useRef, useCallback } from 'react';
import { useState } from 'react';
import type { ReactNode } from 'react';
import type { TMessage } from 'librechat-data-provider';
import ScrollToBottom from '~/components/Messages/ScrollToBottom';
import { useScreenshot, useScrollToRef } from '~/hooks';
import { useScreenshot, useMessageScrolling } from '~/hooks';
import { CSSTransition } from 'react-transition-group';
import { useChatContext } from '~/Providers';
import MultiMessage from './MultiMessage';
export default function MessagesView({
@ -14,54 +13,19 @@ export default function MessagesView({
messagesTree?: TMessage[] | null;
Header?: ReactNode;
}) {
const timeoutIdRef = useRef<NodeJS.Timeout>();
const scrollableRef = useRef<HTMLDivElement | null>(null);
const messagesEndRef = useRef<HTMLDivElement | null>(null);
const [showScrollButton, setShowScrollButton] = useState(false);
const [currentEditId, setCurrentEditId] = useState<number | string | null>(-1);
const { conversation, setAbortScroll } = useChatContext();
const { conversationId } = conversation ?? {};
const { screenshotTargetRef } = useScreenshot();
const [currentEditId, setCurrentEditId] = useState<number | string | null>(-1);
const checkIfAtBottom = useCallback(() => {
if (!scrollableRef.current) {
return;
}
const {
conversation,
scrollableRef,
messagesEndRef,
showScrollButton,
handleSmoothToRef,
debouncedHandleScroll,
} = useMessageScrolling(_messagesTree);
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
const diff = Math.abs(scrollHeight - scrollTop);
const percent = Math.abs(clientHeight - diff) / clientHeight;
const hasScrollbar = scrollHeight > clientHeight && percent >= 0.15;
setShowScrollButton(hasScrollbar);
}, [scrollableRef]);
useLayoutEffect(() => {
const scrollableElement = scrollableRef.current;
if (!scrollableElement) {
return;
}
const timeoutId = setTimeout(checkIfAtBottom, 650);
return () => {
clearTimeout(timeoutId);
};
}, [checkIfAtBottom]);
const debouncedHandleScroll = useCallback(() => {
clearTimeout(timeoutIdRef.current);
timeoutIdRef.current = setTimeout(checkIfAtBottom, 100);
}, [checkIfAtBottom]);
const scrollCallback = () => setShowScrollButton(false);
const { scrollToRef: scrollToBottom, handleSmoothToRef } = useScrollToRef({
targetRef: messagesEndRef,
callback: scrollCallback,
smoothCallback: () => {
scrollCallback();
setAbortScroll(false);
},
});
const { conversationId } = conversation ?? {};
return (
<div className="flex-1 overflow-hidden overflow-y-auto">
@ -86,9 +50,8 @@ export default function MessagesView({
<div ref={screenshotTargetRef}>
<MultiMessage
key={conversationId} // avoid internal state mixture
messageId={conversationId ?? null}
messagesTree={_messagesTree}
scrollToBottom={scrollToBottom}
messageId={conversationId ?? null}
setCurrentEditId={setCurrentEditId}
currentEditId={currentEditId ?? null}
/>