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"
import { backend } from "./reactor"
const { useActorSuspenseInfiniteQuery } = createActorHooks(backend)
import { Suspense } from "react"
import { useActorSuspenseInfiniteQuery } from "./reactor"
function ItemList() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useActorSuspenseInfiniteQuery({
functionName: "get_items",
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
getKeyArgs function Narrows the args that key the cache, e.g. to drop the cursor (optional)

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 TError | null A failed first load is thrown to the Error Boundary. A failed refetch keeps data and sets this
status 'success' | 'error' 'error' after a failed refetch; the component suspends until there is data
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
import { Suspense } from "react"
import { useActorSuspenseInfiniteQuery } from "./reactor"
function ProductList() {
const { data, fetchNextPage, hasNextPage } = useActorSuspenseInfiniteQuery({
functionName: "get_products",
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 { Suspense } from "react"
import { ErrorBoundary, type FallbackProps } from "react-error-boundary"
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="error">
<p>Failed to load items</p>
<pre>{error instanceof Error ? error.message : String(error)}</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"
import { useActorSuspenseInfiniteQuery } from "./reactor"
function InfiniteList() {
const { ref, inView } = useInView()
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useActorSuspenseInfiniteQuery({
functionName: "get_items",
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>
)
}
import { useActorSuspenseInfiniteQuery } from "./reactor"
function ItemNames() {
const { data: items } = useActorSuspenseInfiniteQuery({
functionName: "get_items",
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