Skip to content

Commit 5b804f3

Browse files
Ni-2alsakhaev
authored andcommitted
feat: add pagination to News Monitor
1 parent d800729 commit 5b804f3

File tree

1 file changed

+86
-22
lines changed

1 file changed

+86
-22
lines changed

apps/xen-tg-app/src/components/NewsMonitor.tsx

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,17 @@ import PlusIcon from '@/assets/plus'
22
import SyncIcon from '@/assets/sync'
33
import { API_URL } from '@/env'
44
import { useGoBack } from '@/hooks/useGoBack'
5-
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
5+
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
66
import { formatDistance } from 'date-fns'
7-
import { useState } from 'react'
7+
import { useEffect, useRef, useState } from 'react'
88
import { TSubscription } from '../types'
99
import Layout from './Layout'
1010
import { NewSubscription, Subscription } from './NewsSource'
1111
import Spinner from './Spinner'
1212

13-
const mutationFn = async ({
14-
methodName,
15-
params,
16-
}: {
17-
methodName: string
18-
params?: { [key: string]: string | number }
19-
}) => {
13+
const PAGE_LIMIT = 10
14+
15+
async function query<T, U>(name: string, params: T): Promise<U> {
2016
if (!window.Telegram.WebApp.initData) {
2117
throw new Error('Telegram is not available')
2218
}
@@ -28,7 +24,7 @@ const mutationFn = async ({
2824
},
2925
body: JSON.stringify({
3026
jsonrpc: '2.0',
31-
method: methodName,
27+
method: name,
3228
params: params ?? {},
3329
id: 1,
3430
}),
@@ -40,7 +36,28 @@ const mutationFn = async ({
4036
return data.result
4137
}
4238

43-
const queryFn = (name: string, params?: { [key: string]: string | number }) => async () => {
39+
const queryFn =
40+
(name: string) =>
41+
async ({
42+
pageParam,
43+
}: {
44+
pageParam: {
45+
offset: number
46+
limit: number
47+
}
48+
}): Promise<{ total: number; items: TSubscription[] | null | undefined }> =>
49+
query<
50+
{ offset: number; limit: number },
51+
{ total: number; items: TSubscription[] | null | undefined }
52+
>(name, pageParam)
53+
54+
const mutationFn = async ({
55+
methodName,
56+
params,
57+
}: {
58+
methodName: string
59+
params?: { [key: string]: string | number }
60+
}) => {
4461
if (!window.Telegram.WebApp.initData) {
4562
throw new Error('Telegram is not available')
4663
}
@@ -52,7 +69,7 @@ const queryFn = (name: string, params?: { [key: string]: string | number }) => a
5269
},
5370
body: JSON.stringify({
5471
jsonrpc: '2.0',
55-
method: name,
72+
method: methodName,
5673
params: params ?? {},
5774
id: 1,
5875
}),
@@ -67,22 +84,61 @@ const queryFn = (name: string, params?: { [key: string]: string | number }) => a
6784
const NewsMonitor = () => {
6885
const queryClient = useQueryClient()
6986
const [showNewSubscriptionForm, setShowNewSubscriptionForm] = useState(false)
70-
71-
const { data: subscriptions, isPending } = useQuery<{ items: TSubscription[]; total: number }>({
87+
const sentinelRef = useRef<HTMLDivElement | null>(null)
88+
const observerRef = useRef<IntersectionObserver | null>(null)
89+
const {
90+
data: subscriptions,
91+
// error,
92+
fetchNextPage,
93+
hasNextPage,
94+
isFetching,
95+
isFetchingNextPage,
96+
// status,
97+
} = useInfiniteQuery({
7298
queryKey: ['subscriptions'],
73-
queryFn: queryFn('getSubscriptions', {
99+
queryFn: queryFn('getSubscriptions'),
100+
initialPageParam: {
74101
offset: 0,
75-
limit: 100,
76-
}),
102+
limit: PAGE_LIMIT,
103+
},
104+
getNextPageParam: (lastPage, __, lastPageParam) => {
105+
if (lastPage.total <= lastPageParam.offset + lastPageParam.limit) return
106+
return {
107+
offset: lastPageParam.offset + PAGE_LIMIT,
108+
limit: PAGE_LIMIT,
109+
}
110+
},
77111
})
78112

79113
const { data: nextScanOfSubscriptions, isPending: isPendingNextScanOfSubscriptions } = useQuery<{
80114
nextScanAt: string
81115
}>({
82116
queryKey: ['nextScanOfSubscriptions'],
83-
queryFn: queryFn('getNextScanOfSubscriptions'),
117+
queryFn: () => query<null, { nextScanAt: string }>('getNextScanOfSubscriptions', null),
84118
})
85119

120+
useEffect(() => {
121+
const sentinelEl = sentinelRef.current
122+
if (!sentinelEl) return
123+
observerRef.current?.disconnect()
124+
125+
observerRef.current = new IntersectionObserver(
126+
([entry]) => {
127+
if (entry.isIntersecting && !isFetchingNextPage && hasNextPage) fetchNextPage()
128+
},
129+
{
130+
root: null,
131+
rootMargin: '0px',
132+
threshold: 0.1,
133+
}
134+
)
135+
136+
observerRef.current.observe(sentinelEl)
137+
return () => {
138+
observerRef.current?.disconnect()
139+
}
140+
}, [isFetchingNextPage, fetchNextPage, hasNextPage])
141+
86142
const handleUpdateSubscription = useMutation({
87143
mutationFn,
88144
onSuccess: () => {
@@ -124,15 +180,23 @@ const NewsMonitor = () => {
124180
onClick={() => setShowNewSubscriptionForm(true)}
125181
disabled={showNewSubscriptionForm}
126182
>
127-
{isPending ? <Spinner /> : <PlusIcon />}
183+
{isFetching ? <Spinner /> : <PlusIcon />}
128184
</button>
129185
</div>
130186
{showNewSubscriptionForm ? (
131187
<NewSubscription onClose={() => setShowNewSubscriptionForm(false)} />
132188
) : null}
133-
{subscriptions?.items.map((newsSource) => (
134-
<Subscription key={newsSource.id} subscription={newsSource} />
135-
))}
189+
{subscriptions?.pages.map((group) =>
190+
group?.items?.map((newsSource) => (
191+
<Subscription key={newsSource.id} subscription={newsSource} />
192+
))
193+
)}
194+
<div ref={sentinelRef} />
195+
{hasNextPage ? (
196+
<div className="flex h-10 w-full justify-center">
197+
{isFetching || isFetchingNextPage ? <Spinner /> : null}
198+
</div>
199+
) : null}
136200
</div>
137201
</Layout>
138202
)

0 commit comments

Comments
 (0)