Pagination

It’s often the case that you want to avoid querying an entire list from your API in order to minimize the amount of data transfers over the network. To support this, GraphQL APIs will “paginate” a field, allowing users to query a slice of the list. The strategy used to access slices of a list fall into two categories. Offset-based pagination relies offset and limit arguments and mimics the mechanisms provided by most database engines. Cursor-based pagination is a bi-directional strategy that relies on first/after or last/before arguments and is designed to handle modern pagination features such a infinite scrolling.

Paginated Queries

Regardless of the strategy used, houdini follows a simple pattern: mark the field with @paginate, and provide the “page size” via the first, last or limit arguments to the field. Once you’ve done that, you can wire up your query however you prefer. For example, an inline query supporting offset-based pagination would look something like:

<script lang="ts">
    const userList = graphql(`
        query UserList {
            friends(limit: 10) @paginate {
                id
            }
        }
    `)
</script>

<button on:click={userList.loadNextPage}>
    load next
</button>

{$userList.data.friends.map(friend => friend.id).join(',')}
<script>
    const userList = graphql(`
        query UserList {
            friends(limit: 10) @paginate {
                id
            }
        }
    `)
</script>

<button on:click={userList.loadNextPage}>
    load next
</button>

{$userList.data.friends.map(friend => friend.id).join(',')}

and a field that supports cursor-based pagination starting at the end of the list would look something like:

<script lang="ts">
    const userList = graphql(`
        query UserList {
            friends(last: 10) @paginate {
                edges {
                    node {
                        id
                    }
                }
            }
        }
    `)
</script>

<button on:click={userList.loadNextPage}>
    load next
</button>

{$userList.data.friends.map(friend => friend.id).join(',')}
<script>
    const userList = graphql(`
        query UserList {
            friends(last: 10) @paginate {
                edges {
                    node {
                        id
                    }
                }
            }
        }
    `)
</script>

<button on:click={userList.loadNextPage}>
    load next
</button>

{$userList.data.friends.map(friend => friend.id).join(',')}

If you are paginating a field with a cursor-based strategy (forward or backwards), the current page info can be looked up with the pageInfo field of store’s value:

<script lang="ts">
    const userInfo = graphql(`
        query UserList {
            friends(first: 10) @paginate {
                edges {
                    node {
                        id
                    }
                }
            }
        }
    `)
</script>

{#if $userInfo.pageInfo.hasNextPage}
    <button on:click={userInfo.loadNextPage}>
        load more
    </button>
{/if}
<script>
    const userInfo = graphql(`
        query UserList {
            friends(first: 10) @paginate {
                edges {
                    node {
                        id
                    }
                }
            }
        }
    `)
</script>

{#if $userInfo.pageInfo.hasNextPage}
    <button on:click={userInfo.loadNextPage}>
        load more
    </button>
{/if}

Paginated Fragments

paginatedFragment works very much in the same way except for a few caveats. Consider the following:

<script lang="ts">
    const userWithFriends = paginatedFragment(
        user,
        graphql(`
            fragment UserWithFriends on User {
                friends(first: 10) @paginate {
                    edges {
                        node {
                            id
                        }
                    }
                }
            }
        `)
    )
</script>

{#if $userWithFriends.pageInfo.hasNextPage}
    <button on:click={userWithFriends.loadNextPage}>
        load more
    </button>
{/if}
<script>
    const userWithFriends = paginatedFragment(
        user,
        graphql(`
            fragment UserWithFriends on User {
                friends(first: 10) @paginate {
                    edges {
                        node {
                            id
                        }
                    }
                }
            }
        `)
    )
</script>

{#if $userWithFriends.pageInfo.hasNextPage}
    <button on:click={userWithFriends.loadNextPage}>
        load more
    </button>
{/if}

In order to look up the next page for the user’s friend. We need a way to query the specific user that this fragment has been spread into. In order to pull this off, houdini will try to use a generic Node interface and corresponding query by default but you can change the behavior for any type in your schema.

interface Node {
	id: ID!
}

type Query {
	node(id: ID!): Node
}

In short, this means that, by default, any paginated fragment must be of a type that implements the Node interface or has a custom resolver configured so it can be looked up in the api. You can read more information about the Node interface in this section of the graphql community website.

Keep in mind, this is only a requirement for paginated fragments. If your application only uses paginated queries, you do not need to implement the Node interface and resolver.

Pagination Modes

Houdini supports two different “modes” of pagination: Infinite and SinglePage. You can set the default mode in your houdini config and/or override it on a per-paginate directive basis by passing the mode argument:

  • Infinite should be result when the results of loading one page should be added to the existing value (ie, for an infinite scrolling interface).
  • SinglePage should used in a scenario where you want to display the items, one page at a time.

Mutation Operations

A paginated field can be marked as a potential target for a mutation operation by passing a name argument to the @paginate directive. For more information, see this document.

const userWithFriends = paginatedFragment(
	user,
	graphql(`
		fragment UserWithFriends on User {
			friends(first: 10) @paginate(name: "User_Friends") {
				edges {
					node {
						id
					}
				}
			}
		}
	`)
)