Solved

Relative Cursor Based Pagination with GraphQL and React

Jroyce1180
Pathfinder
86 4 22

I want to implement cursor based pagination have not managed to get the "hasNext" click to return the next page of results. The area I am having trouble with is the fetchMore and updateQuery section. I've followed: 

My GraphQL query works when manually passing the intended variables:

 

const GET_NEXT_PRODUCTS = gql`
query ($cursor: String){
  products(first: 10, after: $cursor) {
    pageInfo {
      hasNextPage
      hasPreviousPage
    }
    edges {
      cursor
      node {
        id
        title
        handle
        description
        images(first: 1) {
          edges {
            node {
              originalSrc
              altText
            }
          }
        }
        variants(first: 1) {
          edges {
            node {
              taxCode
              price
              id
              taxable
            }
          }
        }
      }
    }
  }
}
`

 

 

My Query component is here: 

 

    <Query query={GET_NEXT_PRODUCTS}
      notifyOnNetworkStatusChange
    >
      {({ data, loading, error, fetchMore }) => {
        if (loading) return <div>
          <Frame>
            <Spinner accessibilityLabel="Spinner" size="large" color="teal" />
          </Frame>
        </div>;
        if (error) return <div>
          <Banner
            title={error.message}
            status="critical"
          >
            <p>{error.stack}</p>
          </Banner>
        </div>;
        console.log(data);
        return (
          <Page
            fullWidth
            title="Products"
          >
            <Card>
              <ResourceList
                showHeader
                items={data.products.edges}
                promotedBulkActions={promotedBulkActions}
                bulkActions={bulkActions}
                resourceName={resourceName}
                renderItem={renderItem}
                selectedItems={selectedItems}
                onSelectionChange={setSelectedItems}
              />
            </Card>

            {data.products &&
              data.products.pageInfo.hasNextPage && (
                <Pagination
                  hasPrevious
                  onPrevious={() => {
                    console.log('Previous');
                  }}
                  hasNext
                  onNext={() => {
                    console.log('Clicked Next');
                    console.log(data.products.edges);
                    console.log('0: ' + data.products.edges[0].cursor);
                    console.log('9: ' + data.products.edges[numProducts - 1].cursor);
                    fetchMore({
                      query: GET_NEXT_PRODUCTS,
                      variables: {
                        cursor: cursor
                      },
                      updateQuery: (previousResult, { fetchMoreResult }) => {
                        const previousEntry = previousResult.products;
                        const newProducts = fetchMoreResult.products;
                        const newCursor = fetchMoreResult.products.edges[numProducts - 1].cursor;
                        return {
                          cursor: newCursor,
                          entry: {
                            products: newProducts
                          }
                        };
                      }
                    })
                  }}
                >
                </Pagination>
              )
            }
          </Page>
        );
      }}
    </Query>

 

 

Right now I have just tried implementing the hasNext but also need to understand how to get the hasPrevious and return the earlier page. 

I have not found easy to understand documentation on this specific step and would really appreciate any guidance on how to implement this. 

Accepted Solution (1)

HunkyBill
Shopify Expert
4845 60 547

This is an accepted solution.

I render the cursor value from before and after into the controls for prev and next buttons.

When I get data, if there is a next or previous page I am simply echoing that out as booleans into my components. So if next is true, a click on the next sends the after cursor to the query. I get back the next page. Same for previous. If no previous page exists, the button, she no work. Simple.

The one thing I learned from all this, that I did not know, was it is OK to provide a before or after cursor with a nil String value! With that, I can limit my GQL to only two queries. Before and After. I used to think (naively) that I needed an initializer, to setup the case where before or after are nil. Silly me.

I never use JS and Apollo so I cannot help with that. I just use Axios to send a request for data to my App, and my App returns data. I think Apollo is for fancy smancy Apps way beyond anything I would use with Shopify. It seems nice though. I guess once you get used to it as a thing, it makes sense to use it. I just hate adding an extra 100,000 lines of someone else's JS to a project, and trying to keep up with those Joneses. It is hard enough just keeping up with the basic CRUD.

Custom Shopify Apps built just for you! hunkybill@gmail.com http://www.resistorsoftware.com

View solution in original post

Replies 5 (5)

HunkyBill
Shopify Expert
4845 60 547

This is an accepted solution.

I render the cursor value from before and after into the controls for prev and next buttons.

When I get data, if there is a next or previous page I am simply echoing that out as booleans into my components. So if next is true, a click on the next sends the after cursor to the query. I get back the next page. Same for previous. If no previous page exists, the button, she no work. Simple.

The one thing I learned from all this, that I did not know, was it is OK to provide a before or after cursor with a nil String value! With that, I can limit my GQL to only two queries. Before and After. I used to think (naively) that I needed an initializer, to setup the case where before or after are nil. Silly me.

I never use JS and Apollo so I cannot help with that. I just use Axios to send a request for data to my App, and my App returns data. I think Apollo is for fancy smancy Apps way beyond anything I would use with Shopify. It seems nice though. I guess once you get used to it as a thing, it makes sense to use it. I just hate adding an extra 100,000 lines of someone else's JS to a project, and trying to keep up with those Joneses. It is hard enough just keeping up with the basic CRUD.

Custom Shopify Apps built just for you! hunkybill@gmail.com http://www.resistorsoftware.com
HunkyBill
Shopify Expert
4845 60 547

Oops. I get it now. Apollo would be used by people using JS to do their GraphQL calls. Since I would never use JS for my server, that is probably why I never use Apollo!

Custom Shopify Apps built just for you! hunkybill@gmail.com http://www.resistorsoftware.com
joshcorbett
Shopify Partner
14 0 3

Great answer, gives me a solid direction where to start on pagination. Ran into a hurdle though; before and after in the products query cannot be null, so I can't set my before or after to null until I've clicked my button for next page. Perhaps there's a clever solution to this? My application is unhappy with other attempts of mine.

HunkyBill
Shopify Expert
4845 60 547

The before and after can in fact be nil, and if they are, it does not affect results. So you can call the correct routine in your code, based on knowing where you're at. It is really unfortunate that once again, crap-ass Javascript ruins our good days by being counter intuitive and using nil where we use null or weirdCaseSituations where we might use weird_case_situations, because, well, it was expelled from one guy's brain in the early '90's and has yet to be fixed to be compatible with anything else out there.

 

Custom Shopify Apps built just for you! hunkybill@gmail.com http://www.resistorsoftware.com
joshcorbett
Shopify Partner
14 0 3

Thanks for the quick response, shortly after I wrote my reply I figured out that by removing the `!` from the variable definition it's no longer a required value. My code was `$beforeItemCursor: String!` where it was a non-nullable argument because of the `!`. I've since removed it and it can now be null: `$beforeItemCursor: String`.