Quiltt Logo

How to Query Paginated Data in GraphQL

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

Link to this section#Pagination Schema

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

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

  • count returns the total number of list items.
  • 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 User's Transactions

We can construct a query to return a paginated list of the user's transactions:

query GetTransactions($after: String) { transactionsConnection(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": { "transactionsConnection": { "count": 555, "edges": [ { "cursor": "MQ", "node": { "id": "1eb82216-a2a1-410a-b7df-d2d3a6658174", "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 * as React from 'react' import { gql, useQuery } from '@apollo/client' const Transactions = () => { // Construct the query const TRANSACTIONS_QUERY = gql` query Transactions($after: String, $first: Int) { transactionsConnection(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 { transactionsConnection } = data const { edges, count, pageInfo } = transactionsConnection 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