typescript
70 lines · 2 tabs
Maya Patel
Jan 2026
2 tabs
import { useInfiniteQuery } from '@tanstack/react-query'
import api from '@/services/api'
import { Post, PaginatedResponse } from '@/types'
export function useInfinitePosts() {
return useInfiniteQuery({
queryKey: ['posts', 'infinite'],
queryFn: async ({ pageParam = 1 }) => {
const { data } = await api.get<PaginatedResponse<Post>>('/posts', {
params: { page: pageParam },
})
return data
},
getNextPageParam: (lastPage) => {
const { current_page, total_pages } = lastPage.meta
return current_page < total_pages ? current_page + 1 : undefined
},
initialPageParam: 1,
})
}
import { useEffect, useRef } from 'react'
import { useInfinitePosts } from '@/hooks/useInfinitePosts'
import { PostCard } from './PostCard'
export function PostsList() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts()
const sentinelRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
},
{ rootMargin: '100px' }
)
if (sentinelRef.current) {
observer.observe(sentinelRef.current)
}
return () => observer.disconnect()
}, [hasNextPage, isFetchingNextPage, fetchNextPage])
const posts = data?.pages.flatMap((page) => page.data) ?? []
return (
<div className="space-y-6">
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
{hasNextPage && (
<div ref={sentinelRef} className="py-8 text-center">
{isFetchingNextPage ? (
<div className="spinner"></div>
) : (
<button
onClick={() => fetchNextPage()}
className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
>
Load More
</button>
)}
</div>
)}
</div>
)
}
2 files · typescript
Explain with highlit
Infinite scroll loads more content as users reach the bottom of a list, improving perceived performance over pagination. I use the Intersection Observer API to detect when a sentinel element becomes visible, then trigger the next page fetch. React Query's useInfiniteQuery manages pagination state and combines pages into a flattened list. The getNextPageParam function extracts pagination metadata from the API response to determine if more pages exist. Each new page appends to the existing data array. I also provide a 'Load More' button as a fallback for users who prefer manual control or when JavaScript fails. This pattern works great for feeds, search results, or any long lists.
Related snips
typescript
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
typescript
reliability
retry
by codesnips
2 tabs
ruby
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
rails
turbo
hotwire
by codesnips
4 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
react
axios
api
by Maya Patel
1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
react
zustand
state-management
by Maya Patel
2 tabs
typescript
import React from "react";
type FallbackProps = {
error: Error;
reset: () => void;
};
React Error Boundary + error reporting hook
react
frontend
error-boundary
by codesnips
3 tabs
javascript
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
openAnalyzer: true,
});
/** @type {import('next').NextConfig} */
Next.js bundle analyzer for targeted performance work
nextjs
performance
tooling
by codesnips
4 tabs
Share this code
Here's the card — post it anywhere.