mirror of
https://github.com/mroxso/zelo-news.git
synced 2026-04-10 15:36:50 +02:00
* mkstack upgrade * more changes wip * refactor: replace RelaySelector with RelayListManager in ProfileView, BookmarksPage, FollowingPage, and Nip05ProfilePage * feat: add buffer dependency to package.json and package-lock.json * feat: add SettingsPage and integrate into AppRouter and Header * feat: refactor ZapButton to use Button component for improved styling --------- Co-authored-by: highperfocused <highperfocused@pm.me>
72 lines
2.0 KiB
Markdown
72 lines
2.0 KiB
Markdown
### Infinite Scroll for Nostr Feeds
|
|
|
|
For feed-like interfaces, implement infinite scroll using TanStack Query's `useInfiniteQuery` with Nostr's timestamp-based pagination:
|
|
|
|
```typescript
|
|
import { useNostr } from '@nostrify/react';
|
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
|
|
export function useGlobalFeed() {
|
|
const { nostr } = useNostr();
|
|
|
|
return useInfiniteQuery({
|
|
queryKey: ['global-feed'],
|
|
queryFn: async ({ pageParam, signal }) => {
|
|
const filter = { kinds: [1], limit: 20 };
|
|
if (pageParam) filter.until = pageParam;
|
|
|
|
const events = await nostr.query([filter], {
|
|
signal: AbortSignal.any([signal, AbortSignal.timeout(1500)])
|
|
});
|
|
|
|
return events;
|
|
},
|
|
getNextPageParam: (lastPage) => {
|
|
if (lastPage.length === 0) return undefined;
|
|
return lastPage[lastPage.length - 1].created_at - 1; // Subtract 1 since 'until' is inclusive
|
|
},
|
|
initialPageParam: undefined,
|
|
});
|
|
}
|
|
```
|
|
|
|
Example usage with intersection observer for automatic loading:
|
|
|
|
```tsx
|
|
import { useInView } from 'react-intersection-observer';
|
|
import { useMemo } from 'react';
|
|
|
|
function GlobalFeed() {
|
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGlobalFeed();
|
|
const { ref, inView } = useInView();
|
|
|
|
useEffect(() => {
|
|
if (inView && hasNextPage && !isFetchingNextPage) {
|
|
fetchNextPage();
|
|
}
|
|
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
|
|
// Remove duplicate events by ID
|
|
const posts = useMemo(() => {
|
|
const seen = new Set();
|
|
return data?.pages.flat().filter(event => {
|
|
if (!event.id || seen.has(event.id)) return false;
|
|
seen.add(event.id);
|
|
return true;
|
|
}) || [];
|
|
}, [data?.pages]);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{posts.map((post) => (
|
|
<PostCard key={post.id} post={post} />
|
|
))}
|
|
{hasNextPage && (
|
|
<div ref={ref} className="py-4">
|
|
{isFetchingNextPage && <Skeleton className="h-20 w-full" />}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
``` |