Quiltt Logo

How to Query Paginated Data in GraphQL

The Profile GraphQL API uses Relay-style cursor-based connections to support efficient and flexible querying of large lists like Transactions.

Relay-style connections are similar to basic cursor-based pagination, but have a slightly different response structure:

  • count returns the total number of items in the list.
  • edges returns an array of cursor and node objects, where each node is an item in the list.
  • pageInfo returns an object containing pagination metadata, including startCursor, endCursor, hasPreviousPage and hasNextPage fields to help paginate subsequent queries.

Link to this section#Paginating a Profile's Transactions

We can construct a query to return a paginated list of the Profile's Transactions:

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

Link to this section#Variables

{
  "after": null
}

Link to this section#Response

{
  "data": {
    "transactions": {
      "count": 555,
      "edges": [
        {
          "cursor": "MQ",
          "node": {
            "id": "txn_1vxuyF6nj8O4RnunE1ndqu",
            "description": "Chipotle Mexican Grill",
            "amount": -25,
            "date": "2022-04-05",
          },
          ...
        },
        ...
      ]
    },
    "pageInfo": {
      "startCursor": "MQ",
      "endCursor": "Mw",
      "hasNextPage": true,
      "hasPreviousPage": true
    }
  }
}

The response will return all Transaction nodes with cursors from MQ to Mw. To get the next page, we repeat this query setting the value of after to the value of pageInfo.endCursor.

{
  "after": "Mw"
}

Once pageInfo.hasNextPage returns false, we've completed all the available transactions.

Link to this section#Using Pagination with React + Apollo

In this example, we use the React Apollo Client to create a TransactionsList React component that renders a paginated list of the user's transactions.

import { gql, useQuery } from '@apollo/client'

const Transactions = () => {
  // Construct the query
  const TRANSACTIONS_QUERY = gql`
    query Transactions($after: String, $first: Int) {
      transactions(sort: DATE_DESC, first: $first, after: $after) {
        count
        edges {
          cursor
          node {
            id
            description
            amount
            date
          }
        }
        pageInfo {
          startCursor
          endCursor
          hasNextPage
          hasPreviousPage
        }
      }
    }
  `

  // Initialize variables for first query
  const first = 10
  const after = undefined

  // Set up the Apollo `useQuery` hook
  const { data, error, loading, fetchMore } = useQuery(TRANSACTIONS_QUERY, {
    variables: { after: after, first: first },
  })

  // Handle errors
  if (error) {
    console.log('Something went wrong', error)
  }

  // Display a message while the message is in transit
  if (loading) {
    return <>Loading...</>
  }

  const { transactions } = data
  const { edges, count, pageInfo } = transactions
  const { hasNextPage, endCursor } = pageInfo

  if (count === 0) {
    return <>No transactions found</>
  }

  // Fetch more data on button click
  const handleLoadMore = () => {
    fetchMore({
      variables: { after: endCursor, first: first },
    })
  }

  return (
    <div>
      <p>{`${count} Transactions`}</p>
      <ul>
        {edges.map((transaction) => {
          const { node, cursor } = transaction
          const { description, amount, date, 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>{amount}</p>
              </div>
            </li>
          )
        })}
      </ul>

      {/*  Only show "Load More" button if "hasNextPage" is true and more transactions are expected */}
      {hasNextPage && (
        <button type="button" onClick={handleLoadMore}>
          Load More
        </button>
      )}
    </div>
  )
}

export default Transactions