Infinite Scroll Example with React Apollo for GitHub GraphQL API

Apollo Client is a nice GraphQL client library. It has a React binding and make it easy to build React apps. They have a good documentation site, which includes a recipe about pagination. Although implementing infinite scroll is fairly straightforward based on it, this article describes concrete example code. For this example, the target GraphQL service is GitHub.

Dependency Packages

Here is the list of packages that is used in the example.

There are some other dependencies, but we don’t describe them in deep in this article.

Component code

This is view component code.

const RepoList = ({ data: { loading, error, search, loadMore } }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error, probably not logged in.</p>;
const {
nodes = [],
pageInfo: { hasNextPage } = {},
} = search || {};
return (
<InfiniteScroll
loadMore={loadMore}
hasMore={hasNextPage}
loader={<p>Loading...</p>}
>
<ul>
{nodes.map(item => (
<li key={item.url}><a href={item.url}>{item.name}</a></li>
))}
</ul>
</InfiniteScroll>
);
};

In this code, loadMore is important because it’s not provided as default, but we define it later in the following section.

GraphQL query

const QUERY_REPOS = gql`
query ($q: String!, $end: String) {
search(first: 20, type: REPOSITORY, query: $q, after: $end) {
nodes {
... on Repository {
name
url
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;

This query is very simple, but notice there are two variables and pageInfo is included. Here, after is important because it allows the cursor-based pagination.

HoC by graphql

This is the main code to create a high-order component.

const withQuery = graphql(QUERY_REPOS, {
options: ({ q }) => ({ variables: { q } }),
props: ({ data }) => ({
data: {
...data,
loadMore: () => data.fetchMore({
variables: { end: data.search.pageInfo.endCursor },
updateQuery: (previousResult = {}, { fetchMoreResult = {} }) => {
const previousSearch = previousResult.search || {};
const currentSearch = fetchMoreResult.search || {};
const previousNodes = previousSearch.nodes || [];
const currentNodes = currentSearch.nodes || [];
return {
...previousResult,
search: {
...previousSearch,
nodes: [...previousNodes, ...currentNodes],
pageInfo: currentSearch.pageInfo,
},
};
},
}),
},
}),
});

This code assumes that q is provided as a prop. loadMore is defined here and internally calls fetchMore provided by apollo-client.

The required updatedQuery looks ugly, and we wish there would be some default “merge” functions.

Complete example

If you are interested, please have a look at the complete example in GitHub. It includes a naive authentication logic, and it’s a working example.

Hope this helps somebody.