Skip to content
IC Reactor

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>
)
}

UseActorInfiniteQueryParameters extends react-query’s UseInfiniteQueryOptions, giving you access to all standard react-query options plus our custom actor options.

Option Type Description
functionName string The canister method to call
getArgs (pageParam) => Args Function that returns args for each page
initialPageParam TPageParam Initial page parameter
getNextPageParam function Returns next page param or undefined
Option Type Default Description
getPreviousPageParam function - Returns previous page param for bi-directional
maxPages number - Maximum number of pages to keep in cache
enabled boolean true Whether the query should run
staleTime number 0 Time in ms before data is stale
gcTime number 300000 Time before unused data is GC’d
refetchOnWindowFocus boolean true Refetch on window focus
select function - Transform the data
queryKey array - 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
Property Type Description
data InfiniteData<TData> Object with pages and pageParams arrays
data.pages TData[] Array of all fetched pages
data.pageParams TPageParam[] Array of page parameters used
error Error | null Error if query failed
status 'pending' | 'error' | 'success' Query status
isPending boolean True if no data yet
isFetching boolean True if any fetch in progress
isFetchingNextPage boolean True if fetching next page
isFetchingPreviousPage boolean True if fetching previous page
isRefetching boolean True if refetching all pages
hasNextPage boolean True if more pages available
hasPreviousPage boolean True if previous pages available
fetchNextPage function Fetch the next page
fetchPreviousPage function Fetch the previous page
refetch function Refetch 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
})