Skip to content

Commit 1a9b5f5

Browse files
committed
Extracted page-level pagination to useScrollablePagination hook for centralized use.
1 parent 85978c5 commit 1a9b5f5

File tree

2 files changed

+67
-40
lines changed

2 files changed

+67
-40
lines changed

src/app/[locale]/notification/page.tsx

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react';
55
import NotificationCard from '@/components/shared/Notification/NotificationCard';
66
import Skeleton from '@/components/skeleton/skeleton';
77
import { NotificationType } from '@/constants/types';
8+
import { useScrollablePagination } from '@/hooks/useScrollablePagination';
89
import { getNotifications, updateNotification } from '@/services/notificationApi';
910
import { AppContext } from '../../../../context/AppContextProvider';
1011
import logger from '../../../../logger.config.mjs';
@@ -21,10 +22,8 @@ export default function NotificationPage() {
2122
const [hasFetched, setHasFetched] = useState(false);
2223
const [hasMore, setHasMore] = useState(true);
2324

24-
const debounceTimer = useRef<NodeJS.Timeout | null>(null);
2525
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
2626
const observer = useRef<IntersectionObserver | null>(null);
27-
const loadMoreObserver = useRef<IntersectionObserver | null>(null);
2827

2928
const handleShopItemRef = (node: HTMLElement | null) => {
3029
if (node && observer.current) {
@@ -102,44 +101,17 @@ export default function NotificationPage() {
102101
fetchNotifications();
103102
}, [currentUser?.pi_uid]);
104103

105-
useEffect(() => {
106-
if (!currentUser?.pi_uid || !hasMore) return;
107-
108-
if (loadMoreObserver.current) {
109-
loadMoreObserver.current.disconnect();
110-
}
111-
112-
loadMoreObserver.current = new IntersectionObserver(
113-
(entries) => {
114-
const entry = entries[0];
115-
if (entry.isIntersecting && !isLoading && hasMore) {
116-
setLoading(true);
117-
if (debounceTimer.current) clearTimeout(debounceTimer.current);
118-
119-
debounceTimer.current = setTimeout(() => {
120-
fetchNotifications();
121-
}, 1000); // ⏱️ 1s delay before triggering fetch
122-
}
123-
},
124-
{
125-
root: scrollContainerRef.current, // ✅ use actual DOM ref
126-
rootMargin: '0px',
127-
threshold: 1.0,
128-
}
129-
);
130-
131-
const currentRef = loadMoreRef.current;
132-
if (currentRef) {
133-
loadMoreObserver.current.observe(currentRef);
134-
}
135-
136-
return () => {
137-
if (loadMoreObserver.current && currentRef) {
138-
loadMoreObserver.current.unobserve(currentRef);
139-
}
140-
};
141-
}, [currentUser?.pi_uid, hasMore, notifications]);
142-
104+
useScrollablePagination({
105+
containerRef: scrollContainerRef,
106+
loadMoreRef,
107+
fetchNextPage: async () => {
108+
setLoading(true);
109+
await fetchNotifications();
110+
},
111+
hasMore,
112+
isLoading,
113+
});
114+
143115
return (
144116
<div className="w-full md:w-[500px] md:mx-auto p-4">
145117
<div className="text-center mb-7">

src/hooks/useScrollablePagination.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
interface ScrollablePaginationOptions {
4+
containerRef: React.RefObject<HTMLElement>;
5+
loadMoreRef: React.RefObject<HTMLElement>;
6+
fetchNextPage: () => Promise<void>;
7+
hasMore: boolean;
8+
isLoading: boolean;
9+
debounceMs?: number;
10+
}
11+
12+
export const useScrollablePagination = ({
13+
containerRef,
14+
loadMoreRef,
15+
fetchNextPage,
16+
hasMore,
17+
isLoading,
18+
debounceMs = 1000,
19+
}: ScrollablePaginationOptions) => {
20+
const debounceTimer = useRef<NodeJS.Timeout | null>(null);
21+
const observer = useRef<IntersectionObserver | null>(null);
22+
23+
useEffect(() => {
24+
if (!hasMore || isLoading || !loadMoreRef.current) return;
25+
26+
if (observer.current) {
27+
observer.current.disconnect();
28+
}
29+
30+
observer.current = new IntersectionObserver(
31+
(entries) => {
32+
const entry = entries[0];
33+
if (entry.isIntersecting && hasMore && !isLoading) {
34+
if (debounceTimer.current) clearTimeout(debounceTimer.current);
35+
debounceTimer.current = setTimeout(() => {
36+
fetchNextPage();
37+
}, debounceMs);
38+
}
39+
},
40+
{
41+
root: containerRef.current,
42+
threshold: 1.0,
43+
}
44+
);
45+
46+
const currentRef = loadMoreRef.current;
47+
observer.current.observe(currentRef);
48+
49+
return () => {
50+
if (observer.current && currentRef) {
51+
observer.current.unobserve(currentRef);
52+
}
53+
};
54+
}, [hasMore, isLoading, loadMoreRef.current, containerRef.current]);
55+
};

0 commit comments

Comments
 (0)