| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- 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
|