I turned @Mist's answer into a hook. It's also necessary to pass the data as a dependency to the useEffect
if it is loaded async. Otherwise, we don't store the scroll position when the data arrives.
import { useEffect, useState } from "react";
/**
* Custom hook to maintain the scroll position of a scrollable container when the displayed data changes.
*
* @param scrollContainerRef - A reference to the scrollable container element.
* @param data - The data displayed in the scrollable container. Typically, this is an array. When this data changes, the hook ensures the scroll position is maintained.
*
* @returns void
*/
export default function useMaintainScrollPosition(
scrollContainerRef: React.RefObject<HTMLDivElement>,
data: any
) {
const [prevScrollHeight, setPrevScrollHeight] = useState(0);
useEffect(() => {
const scrollableElement = scrollContainerRef.current;
if (
scrollableElement &&
prevScrollHeight != scrollableElement.scrollHeight
) {
const diff = scrollableElement.scrollHeight - prevScrollHeight;
if (diff) {
console.log({ diff });
setTimeout(() => {
scrollableElement.scrollTo({
top: diff,
});
}, 0);
}
setPrevScrollHeight(scrollableElement.scrollHeight);
}
}, [scrollContainerRef, prevScrollHeight, data]);
}