overleaf/services/web/frontend/js/features/chat/components/infinite-scroll.js

89 lines
2.2 KiB
JavaScript
Raw Normal View History

import { useRef, useEffect, useLayoutEffect } from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
const SCROLL_END_OFFSET = 30
function InfiniteScroll({
atEnd,
children,
className = '',
fetchData,
itemCount,
isLoading,
}) {
const root = useRef(null)
// we keep the value in a Ref instead of state so it can be safely used in effects
const scrollBottomRef = useRef(0)
function setScrollBottom(value) {
scrollBottomRef.current = value
}
function updateScrollPosition() {
root.current.scrollTop =
root.current.scrollHeight -
root.current.clientHeight -
scrollBottomRef.current
}
// Repositions the scroll after new items are loaded
useLayoutEffect(updateScrollPosition, [itemCount])
// Repositions the scroll after a window resize
useEffect(() => {
const handleResize = _.debounce(updateScrollPosition, 400)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
function onScrollHandler(event) {
setScrollBottom(
root.current.scrollHeight -
root.current.scrollTop -
root.current.clientHeight
)
if (event.target !== event.currentTarget) {
// Ignore scroll events on nested divs
// (this check won't be necessary in React 17: https://github.com/facebook/react/issues/15723
return
}
if (shouldFetchData()) {
fetchData()
}
}
function shouldFetchData() {
const containerIsLargerThanContent =
root.current.children[0].clientHeight < root.current.clientHeight
if (atEnd || isLoading || containerIsLargerThanContent) {
return false
} else {
return root.current.scrollTop < SCROLL_END_OFFSET
}
}
return (
<div
ref={root}
onScroll={onScrollHandler}
className={`infinite-scroll ${className}`}
>
{children}
</div>
)
}
InfiniteScroll.propTypes = {
atEnd: PropTypes.bool,
children: PropTypes.element.isRequired,
className: PropTypes.string,
fetchData: PropTypes.func.isRequired,
itemCount: PropTypes.number.isRequired,
isLoading: PropTypes.bool,
}
export default InfiniteScroll