Skip to content

Commit 45d203d

Browse files
committed
feat: add useInfiniteScroll hook
1 parent 3457129 commit 45d203d

File tree

3 files changed

+67
-3
lines changed

3 files changed

+67
-3
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "qhlab-library-template",
2+
"name": "infinite-scroll-hook",
33
"version": "0.0.1",
44
"main": "dist/cjs/index.js",
55
"module": "dist/esm/index.js",
@@ -15,7 +15,7 @@
1515
],
1616
"repository": {
1717
"type": "git",
18-
"url": "https://github.com/QhlabTeam/qhlab-library-template.git"
18+
"url": "https://github.com/iamyoki/infinite-scroll-hook.git"
1919
},
2020
"license": "MIT",
2121
"scripts": {

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from '.'; // change it to your module
1+
export * from './useInfiniteScroll';

src/useInfiniteScroll.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {LegacyRef, useEffect, useRef, useState} from 'react';
2+
3+
type Options = {
4+
offset?: string;
5+
shouldStop?: boolean;
6+
onLoadMore?: () => Promise<void>;
7+
};
8+
9+
export function useInfiniteScroll(options?: Options) {
10+
const {offset = '0px', shouldStop = false, onLoadMore} = options ?? {};
11+
12+
const [isLoading, setIsLoading] = useState(false);
13+
const observerRef = useRef<IntersectionObserver>();
14+
const targetRef = useRef(document.createElement('div'));
15+
16+
const containerRef: LegacyRef<HTMLElement> = (container) => {
17+
if (container) {
18+
container.append(targetRef.current);
19+
container.style.position = 'relative';
20+
}
21+
};
22+
23+
useEffect(() => {
24+
const target = targetRef.current;
25+
target.toggleAttribute('data-infinite-scroll-detector', true);
26+
target.style.position = 'absolute';
27+
target.style.bottom = offset;
28+
if (target.offsetTop < 0) target.style.bottom = '0px';
29+
}, [offset, isLoading]);
30+
31+
useEffect(() => {
32+
const observe = observerRef.current;
33+
if (observe) {
34+
observe.disconnect();
35+
}
36+
37+
async function handler([{isIntersecting}]: IntersectionObserverEntry[]) {
38+
if (
39+
isIntersecting &&
40+
!isLoading &&
41+
!shouldStop &&
42+
typeof onLoadMore === 'function'
43+
) {
44+
setIsLoading(true);
45+
await onLoadMore();
46+
setIsLoading(false);
47+
}
48+
}
49+
50+
observerRef.current = new IntersectionObserver(
51+
handler as IntersectionObserverCallback,
52+
{threshold: 0}
53+
);
54+
55+
observerRef.current.observe(targetRef.current);
56+
57+
return () => observe?.disconnect();
58+
}, [isLoading, onLoadMore, shouldStop]);
59+
60+
return {
61+
isLoading,
62+
containerRef,
63+
};
64+
}

0 commit comments

Comments
 (0)