ReactHustle

Tutorial: How to implement infinite loading with URQL GraphQL

Jasser Mark Arioste

Jasser Mark Arioste

Tutorial: How to implement infinite loading with URQL GraphQL

So you're tasked with implementing an infinite loading pattern on mobile using a graphql schema. Let me share my knowledge on how to implement infinite loading on URQL on a Next.js Project. In this tutorial, I'll be showing you on how to implement infinite loading on URQL with rick and morty api as our backend endpoint

Infinite loading a is very common pattern in mobile layout since it's optimized for mobile UX as opposed to pagination which is more suited for desktop.  There are many ways to implement infinite loading in URQL however today I'm going to show to you probably the most simple pattern to implement infinite loading pattern using a custom hook. 

How does and infinite loading pattern work? #

For infinite loading, we have to create a custom hook that tracks the following: the page number and the new items from graphql cache. Every time the page changes, we have to update the items displayed.

Step 1: Project Setup #

Please see the tutorial on how to setup URQL with Next.js as we'll continue from that tutorial. It also contains setup for the rick and morty api and the episodes list that we'll be using so don't skip that part! Once you finish the setup, proceed to the next step.

Step 2: Modifying the Episodes Query #

Let's modify the episodes query in episodes.graphql by adding information on the next page

#episodes.graphql

query Episodes($page: Int) {
  episodes(page: $page) {
    results {
      air_date
      created
      episode
      name
    }
    #Add this part
    info {
      next
    }
  }
}

12345678910111213141516

Run yarn gen to regenerate the graphql types.

Step 3: Creating the Custom Hook #

Lets create the file first:

#create the hooks folder in the root project directory for all our custom hooks
mkdir hooks && cd hooks 
touch useLoadEpisodes.tsx
123

Insert the code:

//useLoadEpisodes.tsx
import { useEffect, useState } from "react";
import { useEpisodesQuery } from "../graphql/episodes.gql";
import { Episode } from "../graphql/types";

export const useLoadEpisodes = () => {
  const [items, setItems] = useState<Partial<Episode | null>[]>([]);

  const [page, setPage] = useState(1);

  const [{ data, fetching }] = useEpisodesQuery({
    variables: {
      page: page,
    },
  });

  const episodes = data?.episodes?.results ?? [];

  //this is the important part, whenever the episodes changes, we concatenate it to the previous list
  useEffect(() => {
    setItems(items.concat(episodes));
  }, [episodes]);

  const next = data?.episodes?.info?.next;

  //when load more is called, we modify the page so that it triggers another query to the backend.
  const loadMore = () => {
    if (next) {
      setPage(next!);
    }
  };

  //return the important items for our interface
  return {
    items,
    hasNext: Boolean(next),
    loadMore,
    fetching,
  };
};
12345678910111213141516171819202122232425262728293031323334353637383940

Step 4: Implement infinite loading in pages/index.tsx #

Inside pages/index.tsx, we'll make a couple of changes to take into account infinite loading:

//pages/index.tsx

import type { NextPage } from "next";
import { useLoadEpisodes } from "../hooks/useLoadEpisodes";

const Home: NextPage = () => {
  //use the custom hook instead of the generated hook
  const { items, hasNext, loadMore, fetching } = useLoadEpisodes();

  const episodes = items;
  return (
    <div className="container">
      {/* this stays the same */}
      <ol className="episode-list">
        {episodes?.map((episode, i) => {
          return (
            <li key={i} className="episode">
              <h2 className="episode-title">{episode?.name}</h2>
              <p className="episode-ep">{episode?.episode}</p>
              <time dateTime={episode?.air_date ?? ""}>
                {episode?.air_date}
              </time>
            </li>
          );
        })}
      </ol>
      {/* add loading indicator for when we are fetching data from the api */}
      {fetching && <p>Loading...</p>}
      {/* add a load more button and call loadMore when clicked */}
      {/* hide it there are no next pages */}
      {!fetching && hasNext && (
        <button className="load-more" onClick={loadMore}>
          Load more
        </button>
      )}
    </div>
  );
};

export default Home;
12345678910111213141516171819202122232425262728293031323334353637383940

Bonus Step: Style and Refactor your components #

I'll leave this step up to you since it's out of the scope of this tutorial. However, you may check the completed code and the demo below:

Resources #


Credits
- Photo by Rafael Cerqueira from Pexels

Share this post!

Related Posts