-
-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathinfinite-scroll.tsx
76 lines (69 loc) · 2.34 KB
/
infinite-scroll.tsx
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
import * as React from 'react';
interface InfiniteScrollProps {
isLoading: boolean;
hasMore: boolean;
next: () => unknown;
threshold?: number;
root?: Element | Document | null;
rootMargin?: string;
reverse?: boolean;
children?: React.ReactNode;
}
export default function InfiniteScroll({
isLoading,
hasMore,
next,
threshold = 1,
root = null,
rootMargin = '0px',
reverse,
children,
}: InfiniteScrollProps) {
const observer = React.useRef<IntersectionObserver>();
// This callback ref will be called when it is dispatched to an element or detached from an element,
// or when the callback function changes.
const observerRef = React.useCallback(
(element: HTMLElement | null) => {
let safeThreshold = threshold;
if (threshold < 0 || threshold > 1) {
console.warn(
'threshold should be between 0 and 1. You are exceed the range. will use default value: 1',
);
safeThreshold = 1;
}
// When isLoading is true, this callback will do nothing.
// It means that the next function will never be called.
// It is safe because the intersection observer has disconnected the previous element.
if (isLoading) return;
if (observer.current) observer.current.disconnect();
if (!element) return;
// Create a new IntersectionObserver instance because hasMore or next may be changed.
observer.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore) {
next();
}
},
{ threshold: safeThreshold, root, rootMargin },
);
observer.current.observe(element);
},
[hasMore, isLoading, next, threshold, root, rootMargin],
);
const flattenChildren = React.useMemo(() => React.Children.toArray(children), [children]);
return (
<>
{flattenChildren.map((child, index) => {
if (!React.isValidElement(child)) {
process.env.NODE_ENV === 'development' &&
console.warn('You should use a valid element with InfiniteScroll');
return child;
}
const isObserveTarget = reverse ? index === 0 : index === flattenChildren.length - 1;
const ref = isObserveTarget ? observerRef : null;
// @ts-ignore ignore ref type
return React.cloneElement(child, { ref });
})}
</>
);
}