-
Notifications
You must be signed in to change notification settings - Fork 36
/
useInfiniteScroll.js
107 lines (89 loc) · 2.79 KB
/
useInfiniteScroll.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { useEffect, useRef, useState } from 'react';
import useWindowSize from './useWindowSize';
import useInterval from './useInterval';
const WINDOW = 'window';
const PARENT = 'parent';
function useInfiniteScroll({
loading,
hasNextPage,
onLoadMore,
threshold = 150,
checkInterval = 200,
scrollContainer = WINDOW
}) {
const ref = useRef();
const { height: windowHeight, width: windowWidth } = useWindowSize();
// Normally we could use the "loading" prop, but when you set "checkInterval" to a very small
// number (like 10 etc.), some request components can't set its loading state
// immediately (I had this problem with react-apollo's Query component. In some cases, it runs
// "updateQuery" twice). Thus we set our own "listen" state which immeadiately turns to "false" on
// calling "onLoadMore".
const [listen, setListen] = useState(true);
useEffect(() => {
if (!loading) {
setListen(true);
}
}, [loading]);
function getParentSizes() {
const parentNode = ref.current.parentNode;
const parentRect = parentNode.getBoundingClientRect();
const { top, bottom, left, right } = parentRect;
return { top, bottom, left, right };
}
function getBottomOffset() {
const rect = ref.current.getBoundingClientRect();
const bottom = rect.bottom;
let bottomOffset = bottom - windowHeight;
if (scrollContainer === PARENT) {
const { bottom: parentBottom } = getParentSizes();
// Distance between bottom of list and its parent
bottomOffset = bottom - parentBottom;
}
return bottomOffset;
}
function isParentInView() {
const parent = ref.current ? ref.current.parentNode : null;
if (parent) {
const { left, right, top, bottom } = getParentSizes();
if (left > windowWidth) {
return false;
} else if (right < 0) {
return false;
} else if (top > windowHeight) {
return false;
} else if (bottom < 0) {
return false;
}
}
return true;
}
function listenBottomOffset() {
if (listen && !loading && hasNextPage) {
if (ref.current) {
if (scrollContainer === PARENT) {
if (!isParentInView()) {
// Do nothing if the parent is out of screen
return;
}
}
// Check if the distance between bottom of the container and bottom of the window or parent
// is less than "threshold"
const bottomOffset = getBottomOffset();
const validOffset = bottomOffset < threshold;
if (validOffset) {
setListen(false);
onLoadMore();
}
}
}
}
useInterval(
() => {
listenBottomOffset();
},
// Stop interval when there is no next page.
hasNextPage ? checkInterval : 0
);
return ref;
}
export default useInfiniteScroll;