LibreChat/client/src/hooks/Messages/useMessageScrolling.ts
Danny Avila 4674a54c70
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
2023-12-01 19:54:09 -05:00

83 lines
2.5 KiB
TypeScript

import { useRecoilValue } from 'recoil';
import { useLayoutEffect, useState, useRef, useCallback, useEffect } from 'react';
import type { TMessage } from 'librechat-data-provider';
import useScrollToRef from '../useScrollToRef';
import { useChatContext } from '~/Providers';
import store from '~/store';
export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
const autoScroll = useRecoilValue(store.autoScroll);
const timeoutIdRef = useRef<NodeJS.Timeout>();
const scrollableRef = useRef<HTMLDivElement | null>(null);
const messagesEndRef = useRef<HTMLDivElement | null>(null);
const [showScrollButton, setShowScrollButton] = useState(false);
const { conversation, setAbortScroll, isSubmitting, abortScroll } = useChatContext();
const { conversationId } = conversation ?? {};
const checkIfAtBottom = useCallback(() => {
if (!scrollableRef.current) {
return;
}
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);
},
});
useEffect(() => {
if (!messagesTree) {
return;
}
if (isSubmitting && scrollToBottom && !abortScroll) {
scrollToBottom();
}
}, [isSubmitting, messagesTree, scrollToBottom, abortScroll]);
useEffect(() => {
if (scrollToBottom && autoScroll && conversationId !== 'new') {
scrollToBottom();
}
}, [autoScroll, conversationId, scrollToBottom]);
return {
conversation,
scrollableRef,
messagesEndRef,
scrollToBottom,
showScrollButton,
handleSmoothToRef,
debouncedHandleScroll,
};
}