Infinite Queries
Build load-more interfaces with generated infinite query hooks.
Infinite queries are runtime query hooks for "load more" interfaces. They build on normal Query Hooks, but codegen adds pagination handling around a WPGraphQL connection.
WPGraphQL uses cursor pagination. Instead of asking for page 2, you ask for the next items after the last endCursor.
Codegen Requirements
Codegen treats a query as infinite when the operation name contains Infinite, for example UseInfinitePosts.
The query must:
- live under
queries/ - follow the normal
Use*filename and operation-name rules - define
$limit: Intwith a default value - define
$cursor: Stringwith no default value - use
first: $limitandafter: $cursoron the connection - select one
pageInfoobject - select
pageInfo { endCursor hasNextPage } - select
nodesnext to thatpageInfo
query UseInfinitePosts($limit: Int = 6, $cursor: String) {
posts(first: $limit, after: $cursor) {
pageInfo {
endCursor
hasNextPage
}
nodes {
uri
title
}
}
}Hook API
The generated hook returns a TanStack infinite-query result, with the page data flattened for normal rendering.
type InfiniteResult<TNode> = {
data?: {
nodes: TNode[]
total?: number | null
}
hasNextPage: boolean
isFetchingNextPage: boolean
fetchNextPage: () => Promise<unknown>
}Use fetchNextPage() to load the next page.
Example Usage
import { useInfinitePosts } from "@hooks/queries"
export function LatestPosts() {
const lookup = useInfinitePosts(undefined, {
refetchOnWindowFocus: false,
})
return (
<div>
{lookup.isLoading && <p>Loading...</p>}
{lookup.data?.nodes?.length ? (
<ul>
{lookup.data.nodes.map((post) => (
<li key={post.uri}>{post.title}</li>
))}
</ul>
) : null}
{lookup.hasNextPage && (
<button disabled={lookup.isFetchingNextPage} onClick={() => lookup.fetchNextPage()}>
{lookup.isFetchingNextPage ? "Loading..." : "Load more"}
</button>
)}
</div>
)
}Initial Data
Infinite hooks accept initialData, but it must match the original GraphQL query structure, including the selected nodes, pageInfo.hasNextPage, and pageInfo.endCursor fields.
Only use initialData when you already have a matching connection result from a view, block, or app query. Otherwise, let the hook load normally in the browser.