GraphQL Pagination using React, Apollo and GraphCMS

Introduction

GraphQL is a data query language and specification developed courtesy of Facebook. It was open sourced to the masses in 2015 and means you are able to do two things:

  1. Write queries to access a data “shape” – predefined by the API you are accessing
  2. Execute those queries using the GraphQL runtime

In this article, we are going to focus on how to manipulate large data-sets in order to render classic UI pagination elements.

Notes on API design

The GraphQL type system allows for fields to return arrays of values, and even for nested array fields to return their own lists of values. However, there are a number of different design paradigms that an API designer can use for pagination. Each design has its own keywords and structures that will inform the query.

It is common for fields that return a list of items to allow arguments such as first and after or offset (“offset” mimics the possibly the more familiar skip to those who use LINQ or other server-side capabilities). The afterargument usually accepts a unique object identifier of an item in the list.

A pattern called “Connections” has emerged (and been encouraged) as of current best practice. This introduces the concept of “edges” – where each object has a “cursor” (unique identifier). This allows for feature-rich pagination following a “starting edge” and an “ending edge” for each paged result set.

However, what approach is enabled is left entirely up to the API designer. The pagination approach outlined here using Apollo GraphQL and GraphCMS might not be applicable to other APIs you may want to consume. You can take comfort in the fact that we are following the encouraged “best practice” approach.

Connecting to a data-set

We are going to use a dataset that I have set-up for this article. It is a meta GraphQL dataset that lists all open/testable GraphQL endpoints, so hopefully, it will give you some ideas as well as some insight into how we would go about implementing pagination.

Here is the URL: Public GraphQL APIs

The GraphCMS set-up is a neat and easy way to get started with a working GraphQL endpoint for free (on the developer account) and gives you plenty of features to work with. If you have any questions on this, I would be happy to go into them separately!

Pagination

So, as mentioned in the introduction there are a number of different ways we could approach pagination and these are somewhat dependent upon the design of the API that we are trying to access.

Here are some examples of what a paged data-set request might look like:

  • items(first: 2 offset: 2) – this is asking for 2 items on the second “page” (of size 2)
  • items(first: 2 after: $itemId)
  • items(first: 2 after: $itemCursor

It is worth noting that often-times (but not always!), the $itemId and $itemCursor are the same value. Using cursors is generally deemed to be the most powerful of the options available, and allows for flexibility if data items are added to the set or the pagination model changes. It is best practice to see cursors as considered “opaque” values (often base64 encoded) which on their own mean nothing, but are relevant only within the data-set that they are extracted from.

As an example, if you run the following query:

{
  apis: apisConnection(first: 3) {
    edges {
      node {
        name
      }
      cursor
    }
    pageInfo {
      hasNextPage
      startCursor
      endCursor
    }
  }
}

At the provided Graphiql URL, then you will get a response like the following:

{
  "data": {
    "apis": {
      "edges": [
        {
          "node": {
            "name": "Brandfolder"
          },
          "cursor": "cjumqwiws258o0c15qfbvotuh"
        },
        {
          "node": {
            "name": "Braintree"
          },
          "cursor": "cjumqxjfn265u0c15iqr1joct"
        },
        {
          "node": {
            "name": "Buildkite"
          },
          "cursor": "cjumqy3vk26nt0c15epio00m0"
        }
      ],
      "pageInfo": {
        "hasNextPage": true,
        "startCursor": "cjumqwiws258o0c15qfbvotuh",
        "endCursor": "cjumqy3vk26nt0c15epio00m0"
      }
    }
  }
}

It is the pageInfo object that interests us as it highlights the edge cursors that we can use to navigate the list.

This would allow us to hook up the following query to get the next page in the list:

{
  apis: apisConnection(first: 3 after: "cjumqy3vk26nt0c15epio00m0") {
    edges {
      node {
        name
      }
      cursor
    }
    pageInfo {
      hasNextPage
      startCursor
      endCursor
    }
  }
}

Which will, in turn, give us a new set of pageInfo options.

Repeating queries

The question now, then, is how do we go about passing these updated cursors and numbers into the GraphQL queries? We don’t really want to declare multiple Query elements as we’ll be running the same actual Query each time, so how exactly do we get this to work?

At this point, I will introduce the Apollo GraphQL platform which will give us a number of tools and methods to interact with our data-set, including the ability to fetch more content fluently from our API.

To this end, I have set up a super simple React-Gatsby-Apollo solution which you can fork or clone from here on GitHub. Once you have it set up locally, you should be able to run

> gatsby develop

to start the app on port :8000 using the command line.

The code that we want to look at is in the src/components/list.js file. In here you will find a call to the publically available APIs endpoint which makes a basic query:

import gql from 'graphql-tag';

const APOLLO_QUERY = gql`
  query content($first: Int, $skip: Int, $after: String) {
    apis: apisConnection(first: $first, skip: $skip, after: $after) {
      edges {
        node {
          id
          name
          description
          graphiqlLink
          docsLink
          repoLink
          tags
        }
      }
      pageInfo {
        startCursor
        endCursor
      }
    }
    apisConnection {
      aggregate {
        count
      }
    }
  }`;

It is worth noting that there are actually two queries being run here: the apisquery and the aggregate query. This is due to the limitations of the free GraphCMS API – often you can rely on the pageInfo object to contain booleans indicating whether the result set hasNextPage or hasPreviousPagebut at the moment I cannot. As a substitute for this data, I am gathering that information differently by getting the aggregate result count from the API and using it to calculate whether there are prev/next pages instead.

This produces the following results and interactivity:

 

fetchMore()

So, now how are those pagination controls working? Answer: by using the Apollo GraphQL function fetchMore()

import { Query } from 'react-apollo';

return (
  <Query query={APOLLO_QUERY} variables={{"first": this.state.pageSize}}>
      {({ data, loading, error, fetchMore }) => {
        if (loading) return <p>Loading APIs...</p>;
        if (error) return <p>Error: ${error.message}</p>;
        ...
    }}
  </Query>
);

In our component, we are loading the Query class from Apollo and then passing in our query “variables”. We are then asking for some information in return:

  • data – the actual GraphQL query result
  • loading – a boolean variable that allows us to show appropriate UI elements as we wait for results from the API
  • error – an error object that allows us to display the information to the user
  • fetchMore – a function that allows us to pass in updated variables to the existing query and render updated results set

Pagination using the skip argument

In our example, when you click a page number it is actually calculating how many items need to be skipped. This is dependent upon the page size in order to display that result set.

So the onClick code for the page 4 button on a page size of 3 (for example) looks like this:

<button onClick={e => 
  this.setState({
    currentPage: 4
  }, () => {
    fetchMore({
      query: APOLLO_QUERY,
      variables: {"first": 3, "skip": (this.state.currentPage - 1)*3},
      updateQuery: (prev, {fetchMoreResult}) => {
        if (!fetchMoreResult) return prev;
        return fetchMoreResult;
      }
    })
  })}>
  4
</button>

This passes the updated variables into the defined APOLLO_QUERY and in return receives the new set of items (fetchMoreResult) which updates the result set appropriately. If there is no new set for any reason, then the current set is returned (prev).

Pagination using the after argument

In our example, when you click on the “Next Page” button we are using the provided endCursor from the pageInfo object to navigate to the next result set.

We will use the fetchMore() method call as above, only this time instead of calculating the number we want to skip, we can simply pass the right cursor in the variables object.

<button onClick={e => 
  this.setState({
    currentPage: this.state.currentPage+1
  }, () => {
    fetchMore({
      query: APOLLO_QUERY,
      variables: {"first":this.state.pageSize, "after": pageInfo.endCursor},
      updateQuery: (prev, {fetchMoreResult}) => {
        if (!fetchMoreResult) return prev;
        return fetchMoreResult;
      }
    })
  })}>
  Next page
</button>

Onwards!

So that’s a relatively brief explanation and a whistle-stop tour of different pagination techniques used with GraphQL data-sets, and a way that we might utilize the Apollo GraphQL platform to fetch more results based on the current query set.

As you can see, a lot of how you interact with an API is defined by the designer and what set of practices have been chosen to model their data-set. It is my hope that as the platform continues to evolve, these nuances and permutations in availability will progress into a standardized set of operations that center around the “Connections” pattern.

Where to next?

SHARE ON
GraphQL Pagination using React, Apollo and GraphCMS

You May Also Like

Leave a Reply

Your email address will not be published.