Pagination with Query react-apollo & GraphQL

find-replace
Tourist
5 0 2

I'm trying to use apollo fetchMore to fetch paginated results from the graphQL endpoint. Here is what I've got so far. It does the first query successfully, but it does not try to load additional pages. Any ideas on what I'm doing wrong?

 

import gql from "graphql-tag";
import { Query } from "react-apollo";
import { Page } from "@shopify/polaris";

const GET_PRODUCTS = gql`
  query getProducts($cursor: String) {
    products(first: 1, after: $cursor) {
      pageInfo {
        hasNextPage
      }
      edges {
        node {
          id
          title
          descriptionHtml
        }
        cursor
      }
    }
  }
`;

class Index extends React.Component {
  state = {
    products: []
  };

  render() {
    return (
      <Page>
        <Query query={GET_PRODUCTS}>
          {({ data, loading, error, fetchMore }) => {
            <getProducts
              onLoadMore={() => {
                console.log("LOAD MORE");
                fetchMore({
                  variables: {
                    cursor: data.products.edges[0].cursor
                  },
                  updateQuery: (previousResult, { fetchMoreResult }) => {
                    console.log("FETCH MORE RESULTS", fetchMoreResult);
                    if (fetchMoreResult.products.edges.length) {
                      return [
                        ...previousResult.products.edges,
                        ...fetchMoreResults.products.edges
                      ];
                    }
                    return previousResult.products.edges;
                  }
                });
              }}
            />;
            if (loading) return <div>loading products to memory...</div>;
            if (error) return <div>{error.message}</div>;
            return <div>success!</div>;
          }}
        </Query>
      </Page>
    );
  }
}

export default Index;
Replies 4 (4)

find-replace
Tourist
5 0 2

Alright, I re-worked the Query component and seem to have it working. Is there a better way to do this?

 

<Query query={GET_PRODUCTS}>
          {({ data, loading, error, fetchMore }) => {
            if (loading) return <div>loading products to memory...</div>;
            if (error) return <div>{error.message}</div>;
            if (data.products.pageInfo.hasNextPage) {
              fetchMore({
                variables: {
                  cursor:
                    data.products.edges[data.products.edges.length - 1].cursor
                },
                updateQuery: (previousResult, { fetchMoreResult }) => {
                  let combinedData = {
                    products: {
                      pageInfo: { ...fetchMoreResult.products.pageInfo },
                      edges: [
                        ...previousResult.products.edges,
                        ...fetchMoreResult.products.edges
                      ],
                      __typename: fetchMoreResult.products.__typename
                    }
                  };
                  return combinedData;
                }
              });
            }
            console.log("HERE", data);
            return <div>{JSON.stringify(data)}</div>;
          }}
        </Query>
Jroyce1180
Pathfinder
86 4 22

@find-replace I'm having trouble at this same point and haven't been able to get fetchMore working. Did you ever work out a better solution? 

find-replace
Tourist
5 0 2

I ended up with this which works fine.

<Query query={GET_PRODUCTS}>
                {({ data, loading, error, fetchMore, refetch }) => {
                  if (loading) return <Loading />;
                  if (error) {
                    console.log("ERROR", error);
                    return <div>{error.message}</div>;
                  }

                  if (data.products.pageInfo.hasNextPage) {
                    fetchMore({
                      variables: {
                        cursor:
                          data.products.edges[data.products.edges.length - 1]
                            .cursor
                      },
                      updateQuery: (previousResult, { fetchMoreResult }) => {
                        console.log("FETCH MORE", data.products.pageInfo);
                        return {
                          products: {
                            pageInfo: {
                              ...fetchMoreResult.products.pageInfo
                            },
                            edges: [
                              ...previousResult.products.edges,
                              ...fetchMoreResult.products.edges
                            ],
                            __typename: fetchMoreResult.products.__typename
                          }
                        };
                      }
                    });
                  }

                  if (!data.products.pageInfo.hasNextPage) {
                    console.log("DONE", data.products.pageInfo);
                    return (
                      <Find products={data.products.edges} refetch={refetch}>
                        Done
                      </Find>
                    );
                  }

                  return <Loading />;
                }}
              </Query>
Danh11
Shopify Partner
81 2 32

It looks like this solution is pretty much exactly how the Apollo docs recommend when using fetchMore and updateQuery. Although I couldn't work it out until I saw your comment and also this video. The video is good to follow as the API schema he's working with is similar to Shopify's.

 

Your solution to getting the cursor is good too!

It's disappointing that Shopify don't go into detail about how to do this. They have a video talking about cursor pagination, but they manually enter the cursor which is far from useful.

Here's my working code for what it's worth..

 

 

import React from "react";
import { gql, useQuery } from "@apollo/client";


const PRODUCTS_GQL = gql`
  query ProductsGql($cursor: String) {
    products(first: 5, after: $cursor) {
      edges {
        cursor
        node {
          id
          title
          handle
        }
      }
      pageInfo {
        hasNextPage
      }
    }
  }
`;

const ListProducts = () => {
  const { data, error, loading, fetchMore } = useQuery(PRODUCTS_GQL);

  if (error) return <p>Error: {error.message}</p>;
  if (loading || !data) return <p>Loading..</p>;

  return (
    <>
    <button
      onClick={() => {
        fetchMore({
          variables: {
            cursor: data.products.edges[data.products.edges.length - 1].cursor
          },
          updateQuery: (previousResult, { fetchMoreResult }) => {
            let combinedData = {
              products: {
                pageInfo: { ...fetchMoreResult.products.pageInfo},
                edges: [
                  ...previousResult.products.edges,
                  ...fetchMoreResult.products.edges
                ],
                __typename: fetchMoreResult.products.__typename
              }
            }
            return combinedData;
          }
        })
      }}
    >
      Load more
    </button>
      <div>
        {data.products.edges.map(( product ) => (
          <p key={product.node.id}>{product.node.title}</p>
        ))}
      </div>
    </>
  )
}

export default ListProducts;