Skip to main content

How to Query Paginated Data

Pagination is a technique for efficiently presenting a list of items in a way that is easy for the client to browse through.


AnchorRelay Style Pagination

For querying certain data types including Transactions, Quiltt uses Relay-style cursor-based pagination.

Cursor-based pagination (aka keyset pagination) is a common pagination strategy that avoids many of the pitfalls of offset pagination.

Using Relay-style connections is similar to basic cursor-based pagination, but differs in the format of the query response:

  • edges represent a list of { cursor, node } objects, where each edge.node is a list item
  • pageInfo provides the cursors of the first and last items in the edges list - pageInfo.startCursor and pageInfo.endCursor, respectively.

The pageInfo object also contains the boolean properties hasPreviousPage and hasNextPage, which can be used to determine if there are more results available.


AnchorQuery Example

This query will return a paginated set of transactions.

query Transactions($after: String) {
transactionsConnection(after: $after) {
edges {
cursor
node {
id
description
amount
date
}
pageInfo {
endCursor
hasNextPage
startCursor
}
count
}
}
}

The response will mirror the structure of the response:

AnchorResponse

{
"data": {
"transactionsConnection": {
"edges": [
{
"cursor": "MQ",
"node": {
"id": "1eb82216-a2a1-410a-b7df-d2d3a6658174",
"description": "Chipotle Mexican Grill",
"amount": -25,
"date": "2022-04-05",
},
...
},
...
],
"pageInfo": {
"endCursor": "Mw",
"hasNextPage": true,
"startCursor": "MQ"
},
"count": 555
}
}
}

AnchorUsing pagination with React and Apollo

In this example, we can use React with Apollo Client to create a TransactionsList component that returns a paginated list of a user’s transactions, by account.

import * as React from 'react'
import { gql, useQuery } from '@apollo/client'
const Transactions = () => {
// Initialize variables for first query
const accountId = 'some_account_id'
const first = 10
const after = undefined
const TRANSACTIONS_BY_ACCOUNT_QUERY = gql`
query TransactionsByAccount(
$accountId: ID!,
$after: String,
$first: Int
) {
account(id: $accountId) {
id
name
transactionsConnection(
sort: DATE_DESC,
first: $first,
after: $after
) {
edges {
cursor
node {
id
description
amount
date
entryType
status
}
pageInfo {
endCursor
hasNextPage
startCursor
}
}
count
}
}
`
const { data, error, loading, fetchMore } = useQuery(
TRANSACTIONS_BY_ACCOUNT_QUERY, {
variables: { accountId, after, first }
}
)
if (error) {
// Handle errors
}
if (loading) {
return <>Loading...</>
}
const { account } = data || {}
const { transactionsConnection } = account
const { edges, pageInfo } = transactionsConnection
const { hasNextPage, endCursor } = pageInfo
if (!edges.length) {
return <>No transactions found</>
}
// Fetch more data on button click
const handleLoadMore = () => {
fetchMore({
variables: { accountId, after: endCursor, first },
})
}
return (
<div>
<p>{account.name}</p>
<ul>
{edges.map((transaction) => {
const { node, cursor } = transaction
const { description, amount, date, entryType, status } = node
// Return a single transaction as a list item
return (
<li key={cursor}>
<div>
<p>{description}</p>
<p>{date}</p>
<p>{status}</p>
<p>{entryType}</p>
<p>{amount}</p>
</div>
</li>
)
})}
</ul>
{/* Only show "Load More" button if "hasNextPage" is true */}
{hasNextPage && (
<button type="button" onClick={handleLoadMore}>
Load More
</button>
)}
</div>
)
}
export default Transactions