GraphQL Pagination with Apollo V3 - Part 2

GraphQL Pagination with Apollo V3 - Part 2

May 10, 2021

reactapollographqlnodejs

GraphQL Pagination with Apollo V3 - Part 2

Introduction

In the part 1 of this series we covered and implemented pagination in an Apollo GraphQL Server. In this section however, we will be looking at querying and consuming paginated data from the GraphQL server.

Creating a React component with a GraphQL query

Quickly, we’d install and set up our react app using create-react-app, then we create an ApolloClient instance.

import { ApolloClient, InMemoryCache } from "@apollo/client"
import { relayStylePagination } from "@apollo/client/utilities"

const client = new ApolloClient({
  uri: "http://localhost:4000/graphql",
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          books: relayStylePagination(),
        },
      },
    },
  }),
  onError: ({ networkError, graphQLErrors }) => {
    console.log("graphQLErrors", graphQLErrors)
    console.log("networkError", networkError)
  },
})

export default client

Along with the Uri, we have created an InMemoryCache object and provided it to the ApolloClient constructor. As of Apollo Client 3.0, the InMemoryCache class is provided by the @apollo/client package

Apollo Client stores the results of its GraphQL queries in a normalized, in-memory cache. This enables your client to respond to future queries for the same data without sending unnecessary network requests.

Within this object we’d set the type policies. Normally, the cache will generate a unique id for every identifiable object included in the response.

The type policy allows us to customize how the cache generates unique identifiers for that field’s type. In our case we are specifying how the client should store data from repetitive book queries in the cache.

Now inside our main index.js file, we import our ApolloClient instance and ApolloProvider. At last, we use the ApolloProvider component passing in the client object and set our App component as the child component.

import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
import { ApolloProvider } from "@apollo/client"
import client from "./graphql/client"

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
)

Before doing this we ought to have our local GraphQL server running. If you’ve cloned the repository, you can run:

npm run server:start


Books GraphQL Query

We use our server’s book resolver to query the books, we will provide query and cursor dynamic variables that we will pass to our query.

import { gql } from "@apollo/client"

export const GET_BOOKS = gql`
  query books($first: Int, $after: String) {
    books(first: $first, after: $after) {
      pageInfo {
        endCursor
        hasNextPage
      }
      edges {
        cursor
        node {
          title
          subtitle
          price
          url
          image
        }
      }
    }
  }
`

The first parameter will get n number of books per fetch. Our return data consists of pageInfo containing the data for the endCursor that is to be used in our subsequent fetch. The edges containing the node will provide all the book data that is to be displayed on the UI.

Infinite Scroll Books Component

apollo graphql ui Books UI


The Books component will display the fetched books. Also, there will be provision for an infinite scroll mechanism to fetch more data when the user clicks on the load more button.

Remember our GET_BOOKS GraphQL query, we execute this query in the Books component with the useQuery hook from Apollo-Client.

const first = 5

const { error, data, fetchMore, networkStatus } = useQuery(GET_BOOKS, {
  variables: { first },
  notifyOnNetworkStatusChange: true,
})

Here we have initialised first to 5, therefore our app will fetch the first 5 books once it is mounted. We are able to destructure the values - error, data, loading ,networkStatus and the fetchMore function from the useQuery.

The fetchMore function just like the name implies is used to fetch more data.

In the render method, we map the edges containing the node. Within the node is our book data that displays the title, subtitle, book image, book price and url in our list. We can add a loading text or a spinner when the loading state is true, being set to true means Apollo Client is currently fetching data from the network.

{
  data.books.edges.map(edge => (
    <div className="book_row">
      <img loading="lazy"  src={edge.node.image} alt={edge.node.title} />
      <a href={edge.node.url}>{edge.node.title}</a>
      <span>{edge.node.subtitle}</span>
      <span>{edge.node.price}</span>
    </div>
  ))
}

With this set up, we’ll have the “load more” button just below our rendered data. Clicking the button should populate the rows with the next 5 books from the server.

{
  hasNextPage && (
    <div className="more_button">
      <button
        id="buttonLoadMore"
        disabled={isRefetching}
        loading={isRefetching}
        onClick={() =>
          fetchMore({
            variables: {
              first,
              after: data.books.pageInfo.endCursor,
              delay,
            },
          })
        }
      >
        load more
      </button>
    </div>
  )
}

The code above checks if the hasNextPage (the flag from our previous request that specifies if there exist more values other than that which was returned) is set to true. This check is necessary because we only want the button rendered when there are more books to display.

The onClick handler on the “load more” button calls the fetchMore function. Then we pass in the after variable (this is basically the endCursor from our previous request, found within the pageInfo). You can view the complete code on the repo.

And that’s it, it might come of as a little bit anticlimactic but we’re done. Our app by the looks of it is able to fetch books from the GraphQL server and render the paginated results on our UI :)

Conclusion

We can agree that pagination is compulsory when querying a very large dataset. And for this, the Apollo GraphQL client provides out of the box, ways for automatically tracking the progress of a query execution (when it’s loading, when there’s an error), then the fetchMore function that helps in automatically getting the next set of results.

Not to forget the caching mechanism and persistence for when loading data repeatedly.

There are different ways to build a GraphQL pagination query. In this article we covered the cursor based pagination approach, you can learn more on the other approaches and their pros and cons by checking the official GraphQL documentation.

You can always view and clone the complete code on the Github repo.

Antstack Blog Post
Antstack Blog Post

Are you planning to go to serverless? AntStack is a cloud computing service and consulting company primarily focusing on Serverless Computing. We help companies get up and running with serverless, and we’ll make sure that there are no limits.

Keep track of our socials and connect with us - LinkedIn

event bannerevent bannerevent banner