All Articles

Infinite scrolling in Apollo GraphQL and GraphQL-ruby

Definition of Infinite scrolling from Google.

What is infinite scroll?

Infinite scrolling is a web-design technique that loads content continuously as the user scrolls down the page, eliminating the need for pagination.

We can see infinite scrolling is used by many websites these days eg: Twitter, Facebook uses infinite scroll to show huge chunk of data in small batches and load new data to the DOM as user keeps scrolling down. Infinite scroll ofcourse reduces the load on the server and provides great user experience.

To add infinite scrolling in React.js using Apollo GraphQL and GraphQL-ruby. Firstly, let’s add Cursor based pagination in GraphQL-ruby. Read more about adding pagination here.

Add react-infinite-scroller to infinitely load content using a React Component.

yarn add react-infinite-scroller

Now, let’s write a GraphQL query to fetch the all first ten posts from the server.

# apps/javascript/components/Post/queries.jsx
import gql from "graphql-tag";

export const FETCH_POSTS = gql`
  query FetchPosts($first: Int, $after: String) {
    posts(first: $first, after: $after) {
      pageInfo {
        endCursor
        startCursor
        hasPreviousPage
        hasNextPage
      }
      nodes {
        id
        title
        content
      }
    }
  }
`

Add a React component to list all the posts with infinite scrolling. Apollo GraphQL provides fetchMore API to fetch more data from server and update connection result in the cache with initially pulled data.

# apps/javascript/components/Post/index.jsx
import React from "react";
import { useQuery } from '@apollo/react-hooks';
import InfiniteScroll from 'react-infinite-scroller';

import { FETCH_POSTS } from "./queries";

const DEFAULT_PAGE_SIZE = 10;

export default function PostIndex(props) {
  const { data, loading, error, fetchMore } = useQuery(FETCH_POSTS);

  if (loading) return <p>Loading...</p>;
  const { posts: { nodes, pageInfo } } = data;

  function handleLoadMore() {
    // fetchMore data from server.
  }

  return (
    <div style="height:700px;overflow:auto;">
      <InfiniteScroll
        pageStart={0}
        loadMore={handleLoadMore}
        hasMore={pageInfo.hasNextPage}
        loader={<div className="loader" key={0}>Loading ...</div>}
        useWindow={false}
      >
        {
          nodes.map((post) => (
            <li key={post.id}>{post.title}</li>
          ))
        }
      </InfiniteScroll>
  </div>
  );
}

Function handleLoadMore is triggered when the user scrolls down the page, which fetches the new set of records after the current page endCursor. The new paginated list data is updated to the collection.

export default function PostIndex(props) {
  ...

  function handleLoadMore() {
    fetchMore({
      variables: {
        after: pageInfo.endCursor,
        first: DEFAULT_PAGE_SIZE,
      },
      updateQuery(previousResult, { fetchMoreResult }) {
        const connection = fetchMoreResult.posts;

        return {
          posts: {
            pageInfo: connection.pageInfo,
            nodes: [...previousResult.posts.nodes, ...connection.nodes],
            __typename: previousResult.posts.__typename,
          },
        };
      },
    });
  }

  ...
}

Happy Coding!!