Infinite Queries
InfiniteQueryObserver handles "load more" and infinite scroll. Instead of one value, the cached data is { pages: [...], pageParams: [...] }, and each fetch appends (or prepends) a page.
Basic Usage
import { QueryClient, InfiniteQueryObserver } from "@domphy/query"
import { toState } from "@domphy/core"
const queryClient = new QueryClient()
queryClient.mount()
const items = toState<Item[]>([])
const hasMore = toState(false)
const loadingMore = toState(false)
const observer = new InfiniteQueryObserver<{ items: Item[]; nextCursor: number | null }>(queryClient, {
queryKey: ["feed"],
queryFn: ({ pageParam }) =>
fetch(`/api/feed?cursor=${pageParam}`).then((res) => res.json()),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor, // null/undefined = no more pages
})
observer.subscribe((result) => {
items.set(result.data?.pages.flatMap((page) => page.items) ?? [])
hasMore.set(result.hasNextPage)
loadingMore.set(result.isFetchingNextPage)
})
Flattening pages into one toState list keeps the UI a plain keyed list:
const App: DomphyElement<"div"> = {
div: [
{
ul: (l) => items.get(l).map((item) => ({
li: item.title,
_key: item.id,
})),
},
{
button: (l) => (loadingMore.get(l) ? "Loading..." : "Load more"),
$: [button()],
hidden: (l) => !hasMore.get(l),
onClick: () => observer.fetchNextPage(),
},
],
}
Page Parameters
initialPageParam— the param for the first page (required)getNextPageParam(lastPage, allPages, lastPageParam, allPageParams)— returns the next param, ornull/undefinedwhen there are no more pagesgetPreviousPageParam— same, for bidirectional lists (fetchPreviousPage())
Result Additions
On top of the normal query result:
data.pages,data.pageParamshasNextPage,hasPreviousPagefetchNextPage(),fetchPreviousPage()isFetchingNextPage,isFetchingPreviousPage
Refetching Behavior
When an infinite query refetches (invalidation, window focus), every fetched page is refetched sequentially from the first, so cursors stay consistent. Use maxPages to cap how many pages stay in cache:
new InfiniteQueryObserver(queryClient, {
queryKey: ["feed"],
queryFn: fetchPage,
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
maxPages: 5, // keeps memory and refetch cost bounded
})