Pagination In React Apollo Client

React

GraphQL

15/12/2022


Continuing with the theme of fetching hundreds of events: In this post, you'll learn how to implement pagination on the client-side with Apollo client and infinite scrolling.

This example describes the implementation details of cursor-based pagination on the client side. For more information on the database and GraphQL implementation, check out the previous pagination post.

/> Go to previous post

Query document

While the events query is defined in a separate GraphQL schema file, we need a query document to tell Apollo what event data we are requesting.

GRAPHQL
query events($filter: EventsInput!) {
events(filter: $filter) {
edges {
id
title
# More fields, etc...
}
pageInfo {
endCursor
hasNextPage
}
}
}

Initial query

For the initial request, we only need to pass in the limiter as a variable to the EventsDocument query.

JSX
const {
data,
// Needed for paginating results later on.
fetchMore,
networkStatus,
} = useQuery(EventsDocument, {
variables: {
input: {
// `LIMIT` may represent any number.
first: LIMIT,
},
},
notifyOnNetworkStatusChange: true,
})

Make note of notifyOnNetworkStatusChange. By setting it to true in the options parameter of useQuery, we get more fine-grained information on the network status with networkStatus. This is necessary to show the appropriate loading indicator, as we will make two different types of requests.

For the initial request, we would check for the network status loading. However, later on, when we fetch more data using fetchMore, we would check a status of fetchMore.

JSX
if (networkStatus === NetworkStatus.loading || !data || !data.events) {
return <SpinningCircle />
}

Displaying data

Displaying the data is as simple as iterating it with the map method. No magic here.

However, keep in mind we would like to implement infinite scrolling. This means when we approach the final event in the list, we need to fetch more events. How do we do this?

There's a great library called React Waypoint that allows us to trigger a function as soon as a Waypoint element (i.e. after the final event) enters the viewport.

JSX
<div>
{data.events.edges.map((event, index, thisArray) => (
<div key={event.id}>
<EventCard event={event} />
{/* Add a Waypoint element after the last element */}
{index === thisArray.length - 1 && <Waypoint onEnter={fetchMoreEvents} />}
</div>
))}
{networkStatus === NetworkStatus.fetchMore && <SpinningCircle />}
</div>

When the Waypoint element enters the viewport, it triggers the fetchMoreEvents function, which calls the fetchMore function from useQuery. Notice how we display a loading spinner when we fetch more data and check the network for a fetchMore status.

Fetching paginated data

After fetchMoreEvents is triggered, we call fetchMore only once we've confirmed that there is actually more data to be found using hasNextPage. Once we fetch, we pass in the cursor information in addition to the limiter.

JSX
const {
events: { edges, pageInfo },
} = data
const fetchMoreEvents = () => {
if (!pageInfo.hasNextPage) {
return
}
fetchMore({
variables: {
first: LIMIT,
cursor: pageInfo.endCursor,
},
updateQuery: (prevResult, { fetchMoreResult }) => {
fetchMoreResult.events.edges = [
...prevResult.events.edges,
...fetchMoreResult.events.edges,
]
return fetchMoreResult
},
})
}

Pay special attention to updateQuery. ⚠ By default, if we query new data, our existing events will simply be overwritten by the new data. We do not want this!

Instead, we want to merge the our existing data into to newly fetched set of data. In the function, we are merging our existing results prevResults.events.edges into our new results fetchMoreResult.events.edges, which we then return.


WRITTEN BY

Code and stuff