eddev

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: Int with a default value
  • define $cursor: String with no default value
  • use first: $limit and after: $cursor on the connection
  • select one pageInfo object
  • select pageInfo { endCursor hasNextPage }
  • select nodes next to that pageInfo
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.

On this page