|
|
@@ -1,9 +1,17 @@
|
|
|
import { useRef, useEffect, useLayoutEffect } from 'react'
|
|
|
-import PropTypes from 'prop-types'
|
|
|
import _ from 'lodash'
|
|
|
|
|
|
const SCROLL_END_OFFSET = 30
|
|
|
|
|
|
+interface InfiniteScrollProps {
|
|
|
+ atEnd?: boolean
|
|
|
+ children: React.ReactElement
|
|
|
+ className?: string
|
|
|
+ fetchData(): void
|
|
|
+ itemCount: number
|
|
|
+ isLoading?: boolean
|
|
|
+}
|
|
|
+
|
|
|
function InfiniteScroll({
|
|
|
atEnd,
|
|
|
children,
|
|
|
@@ -11,20 +19,22 @@ function InfiniteScroll({
|
|
|
fetchData,
|
|
|
itemCount,
|
|
|
isLoading,
|
|
|
-}) {
|
|
|
- const root = useRef(null)
|
|
|
+}: InfiniteScrollProps) {
|
|
|
+ const root = useRef<HTMLDivElement>(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) {
|
|
|
+ function setScrollBottom(value: number) {
|
|
|
scrollBottomRef.current = value
|
|
|
}
|
|
|
|
|
|
function updateScrollPosition() {
|
|
|
- root.current.scrollTop =
|
|
|
- root.current.scrollHeight -
|
|
|
- root.current.clientHeight -
|
|
|
- scrollBottomRef.current
|
|
|
+ if (root.current) {
|
|
|
+ root.current.scrollTop =
|
|
|
+ root.current.scrollHeight -
|
|
|
+ root.current.clientHeight -
|
|
|
+ scrollBottomRef.current
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// Repositions the scroll after new items are loaded
|
|
|
@@ -39,23 +49,29 @@ function InfiniteScroll({
|
|
|
}
|
|
|
}, [])
|
|
|
|
|
|
- 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 onScrollHandler(event: React.UIEvent<HTMLDivElement>) {
|
|
|
+ if (root.current) {
|
|
|
+ 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() {
|
|
|
+ if (!root.current) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
const containerIsLargerThanContent =
|
|
|
root.current.children[0].clientHeight < root.current.clientHeight
|
|
|
if (atEnd || isLoading || containerIsLargerThanContent) {
|
|
|
@@ -76,13 +92,4 @@ function InfiniteScroll({
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-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
|