Skip to content

useActorInfiniteQuery

useActorInfiniteQuery is a React hook for fetching paginated data from Internet Computer canisters. It wraps TanStack Query’s useInfiniteQuery with canister-specific functionality.

import { createActorHooks } from "@ic-reactor/react"
const { useActorInfiniteQuery } = createActorHooks(backend)
function ItemList() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useActorInfiniteQuery({
functionName: "getItems",
getArgs: (pageParam) => [{ offset: pageParam, limit: 10 }] as const,
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextOffset ?? undefined,
})
return (
<div>
{data?.pages.map((page, i) => (
<div key={i}>
{page.items.map((item) => (
<Item key={item.id} data={item} />
))}
</div>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? "Loading more..."
: hasNextPage
? "Load More"
: "No more items"}
</button>
</div>
)
}
OptionTypeDescription
functionNamestringThe canister method to call
getArgs(pageParam) => ArgsFunction that returns args for each page
initialPageParamTPageParamInitial page parameter
getNextPageParamfunctionReturns next page param or undefined
OptionTypeDefaultDescription
getPreviousPageParamfunction-Returns previous page param for bi-directional
maxPagesnumber-Maximum number of pages to keep in cache
enabledbooleantrueWhether the query should run
staleTimenumber0Time in ms before data is stale
gcTimenumber300000Time before unused data is GC’d
refetchOnWindowFocusbooleantrueRefetch on window focus
selectfunction-Transform the data
queryKeyarray-Additional query key segments
getArgs: (pageParam: TPageParam) => Args
getNextPageParam: (
lastPage: TData,
allPages: TData[],
lastPageParam: TPageParam,
allPageParams: TPageParam[]
) => TPageParam | undefined | null
getPreviousPageParam: (
firstPage: TData,
allPages: TData[],
firstPageParam: TPageParam,
allPageParams: TPageParam[]
) => TPageParam | undefined | null
PropertyTypeDescription
dataInfiniteData<TData>Object with pages and pageParams arrays
data.pagesTData[]Array of all fetched pages
data.pageParamsTPageParam[]Array of page parameters used
errorError | nullError if query failed
status'pending' | 'error' | 'success'Query status
isPendingbooleanTrue if no data yet
isFetchingbooleanTrue if any fetch in progress
isFetchingNextPagebooleanTrue if fetching next page
isFetchingPreviousPagebooleanTrue if fetching previous page
isRefetchingbooleanTrue if refetching all pages
hasNextPagebooleanTrue if more pages available
hasPreviousPagebooleanTrue if previous pages available
fetchNextPagefunctionFetch the next page
fetchPreviousPagefunctionFetch the previous page
refetchfunctionRefetch all pages
const { data, fetchNextPage, hasNextPage } = useActorInfiniteQuery({
functionName: "getItems",
getArgs: (offset) => [{ offset, limit: 20 }],
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
// If we got fewer items than limit, no more pages
return lastPage.items.length < 20 ? undefined : allPages.length * 20 // Next offset
},
})
const { data, fetchNextPage, hasNextPage } = useActorInfiniteQuery({
functionName: "getItems",
getArgs: (cursor) => [cursor ? { after: cursor } : {}],
initialPageParam: null as string | null,
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
})
const { data, fetchNextPage, hasNextPage } = useActorInfiniteQuery({
functionName: "getItemsByPage",
getArgs: (page) => [{ page, perPage: 10 }],
initialPageParam: 1,
getNextPageParam: (lastPage, allPages, lastPageParam) => {
return lastPage.hasMore ? lastPageParam + 1 : undefined
},
})
const { data, fetchNextPage, fetchPreviousPage, hasNextPage, hasPreviousPage } =
useActorInfiniteQuery({
functionName: "getTimeline",
getArgs: (timestamp) => [{ around: timestamp, limit: 20 }],
initialPageParam: Date.now(),
getNextPageParam: (lastPage) => lastPage.oldestTimestamp,
getPreviousPageParam: (firstPage) => firstPage.newestTimestamp,
})
return (
<div>
<button onClick={() => fetchPreviousPage()} disabled={!hasPreviousPage}>
Load newer
</button>
{data?.pages.map((page, i) => (
<TimelineItems key={i} items={page.items} />
))}
<button onClick={() => fetchNextPage()} disabled={!hasNextPage}>
Load older
</button>
</div>
)
import { useInView } from "react-intersection-observer"
import { useEffect } from "react"
function InfiniteList() {
const { ref, inView } = useInView()
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useActorInfiniteQuery({
functionName: "getItems",
getArgs: (offset) => [{ offset, limit: 20 }],
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) =>
lastPage.items.length < 20 ? undefined : allPages.length * 20,
})
// Auto-fetch when sentinel comes into view
useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage])
return (
<div>
{data?.pages.map((page, i) => (
<div key={i}>
{page.items.map((item) => (
<ItemCard key={item.id} item={item} />
))}
</div>
))}
{/* Sentinel element */}
<div ref={ref}>{isFetchingNextPage && <LoadingSpinner />}</div>
</div>
)
}
const { data: items } = useActorInfiniteQuery({
functionName: "getItems",
getArgs: (offset) => [{ offset, limit: 20 }],
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) =>
lastPage.items.length < 20 ? undefined : allPages.length * 20,
// Flatten all pages into a single array
select: (data) => ({
pages: data.pages.flatMap((page) => page.items),
pageParams: data.pageParams,
}),
})
// items.pages is now a flat array of all items
const { data } = useActorInfiniteQuery({
functionName: "getItems",
getArgs: (offset) => [{ offset, limit: 20 }],
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) =>
lastPage.items.length < 20 ? undefined : allPages.length * 20,
maxPages: 3, // Only keep last 3 pages in cache
})