Skip to content

useActorSuspenseInfiniteQuery

useActorSuspenseInfiniteQuery is a React hook for fetching paginated data from canisters with Suspense support. It suspends the component until the first page is loaded.

import { createActorHooks } from "@ic-reactor/react"
const { useActorSuspenseInfiniteQuery } = createActorHooks(backend)
import { Suspense } from "react"
function ItemList() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useActorSuspenseInfiniteQuery({
functionName: "getItems",
getArgs: (pageParam) => [{ offset: pageParam, limit: 10 }],
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) =>
lastPage.items.length < 10 ? undefined : allPages.length * 10,
})
// data.pages is ALWAYS defined - no undefined check needed
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..." : hasNextPage ? "Load More" : "End"}
</button>
</div>
)
}
// Must be wrapped in Suspense
function App() {
return (
<Suspense fallback={<LoadingSkeleton />}>
<ItemList />
</Suspense>
)
}

Same as useActorInfiniteQuery except:

OptionTypeDescription
functionNamestringThe canister method to call
getArgsfunctionReturns args for each page
initialPageParamTPageParamInitial page parameter
getNextPageParamfunctionReturns next page param
getPreviousPageParamfunctionReturns previous page param (optional)
maxPagesnumberMax pages to cache (optional)
staleTimenumberTime before data is stale

Returns a TanStack Query suspense infinite result. Key difference from useActorInfiniteQuery:

PropertyTypeDescription
dataInfiniteData<TData>Always defined (never undefined)
data.pagesTData[]Array of all pages (at least one)
data.pageParamsTPageParam[]Array of page parameters
errorneverErrors are thrown, not returned
status'success'Always success
hasNextPagebooleanMore pages available
hasPreviousPagebooleanPrevious pages available
fetchNextPagefunctionFetch next page
fetchPreviousPagefunctionFetch previous page
isFetchingNextPagebooleanFetching next page
isFetchingPreviousPagebooleanFetching previous page
function ProductList() {
const { data, fetchNextPage, hasNextPage } = useActorSuspenseInfiniteQuery({
functionName: "getProducts",
getArgs: (page) => [{ page, perPage: 20 }],
initialPageParam: 1,
getNextPageParam: (lastPage, allPages, lastPageParam) =>
lastPage.hasMore ? lastPageParam + 1 : undefined,
})
// data is guaranteed to have at least one page
return (
<div>
{data.pages
.flatMap((page) => page.products)
.map((product) => (
<ProductCard key={product.id} product={product} />
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()}>Load More</button>
)}
</div>
)
}
function App() {
return (
<Suspense fallback={<ProductListSkeleton />}>
<ProductList />
</Suspense>
)
}
import { ErrorBoundary } from "react-error-boundary"
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div className="error">
<p>Failed to load items</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Retry</button>
</div>
)
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Suspense fallback={<Loading />}>
<ItemList />
</Suspense>
</ErrorBoundary>
)
}
import { useInView } from "react-intersection-observer"
import { useEffect, Suspense } from "react"
function InfiniteList() {
const { ref, inView } = useInView()
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useActorSuspenseInfiniteQuery({
functionName: "getItems",
getArgs: (offset) => [{ offset, limit: 20 }],
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) =>
lastPage.items.length < 20 ? undefined : allPages.length * 20,
})
useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage])
return (
<div>
{data.pages
.flatMap((page) => page.items)
.map((item) => (
<ItemCard key={item.id} item={item} />
))}
<div ref={ref}>{isFetchingNextPage && <LoadingSpinner />}</div>
</div>
)
}
function App() {
return (
<Suspense fallback={<ListSkeleton />}>
<InfiniteList />
</Suspense>
)
}
const { data: items } = useActorSuspenseInfiniteQuery({
functionName: "getItems",
getArgs: (offset) => [{ offset, limit: 20 }],
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) =>
lastPage.items.length < 20 ? undefined : allPages.length * 20,
select: (data) => data.pages.flatMap((page) => page.items),
})
// items is a flat array of all items across all pages
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)

Use Suspense Infinite Query when:

  • You always need paginated data (no conditional fetching)
  • You want automatic loading states at the boundary level
  • You’re building a feed or list that requires pagination

Use regular useActorInfiniteQuery when:

  • You need conditional fetching (enabled option)
  • You want per-component loading states
  • You need access to isPending state