mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-09-23 17:56:07 +00:00
Merge branch 'infiniteScroll' into 'main'
Add context for pagination (i.e., infinite feed scroll) See merge request soapbox-pub/mkstack!16
This commit is contained in:
commit
d1744b7d71
64
AGENTS.md
64
AGENTS.md
@ -303,6 +303,70 @@ function usePosts() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Infinite Scroll for 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';
|
||||||
|
|
||||||
|
function GlobalFeed() {
|
||||||
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGlobalFeed();
|
||||||
|
const { ref, inView } = useInView();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inView && hasNextPage) {
|
||||||
|
fetchNextPage();
|
||||||
|
}
|
||||||
|
}, [inView, hasNextPage, fetchNextPage]);
|
||||||
|
|
||||||
|
const posts = data?.pages.flat() || [];
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Efficient Query Design
|
#### Efficient Query Design
|
||||||
|
|
||||||
**Critical**: Always minimize the number of separate queries to avoid rate limiting and improve performance. Combine related queries whenever possible.
|
**Critical**: Always minimize the number of separate queries to avoid rate limiting and improve performance. Combine related queries whenever possible.
|
||||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -56,6 +56,7 @@
|
|||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
|
"react-intersection-observer": "^9.16.0",
|
||||||
"react-resizable-panels": "^2.1.3",
|
"react-resizable-panels": "^2.1.3",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
@ -6627,6 +6628,21 @@
|
|||||||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-intersection-observer": {
|
||||||
|
"version": "9.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz",
|
||||||
|
"integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
|
"react-intersection-observer": "^9.16.0",
|
||||||
"react-resizable-panels": "^2.1.3",
|
"react-resizable-panels": "^2.1.3",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user