Mastering Database Testing with Jest and SuperTest: A Hands-On Approach for PostgreSQL
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
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.