Skip to content
IC Reactor

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

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

Same as useActorInfiniteQuery except:

Option Type Description
functionName string The canister method to call
getArgs function Returns args for each page
initialPageParam TPageParam Initial page parameter
getNextPageParam function Returns next page param
getPreviousPageParam function Returns previous page param (optional)
maxPages number Max pages to cache (optional)
staleTime number Time before data is stale

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

Property Type Description
data InfiniteData<TData> Always defined (never undefined)
data.pages TData[] Array of all pages (at least one)
data.pageParams TPageParam[] Array of page parameters
error never Errors are thrown, not returned
status 'success' Always success
hasNextPage boolean More pages available
hasPreviousPage boolean Previous pages available
fetchNextPage function Fetch next page
fetchPreviousPage function Fetch previous page
isFetchingNextPage boolean Fetching next page
isFetchingPreviousPage boolean Fetching 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