reactjsdatabasegraphqlapollo-clientreact-apollo

How to implement cursor based pagination when elements can be deleted, which can invalidate the stored cursor?


I have a GraphQL query which looks like so

query Person($cursor: ID, $limit: Int) {
  items:{
     ...
  }
  name
  ...
  cursor
}

While making the query for the next page, I fetch using the returned cursor.

However, the user can also mutate these items. Let’s say the user deletes an item. I can use either writeFragment or cache.modify directly to delete it from the array. But the trouble occurs when I need to fetch more data.

From what I’ve noticed by playing around with the API, the cursor maps to the items. For example, say I delete the 2nd last item. The cursor to fetch more still remains the same. However, if I delete the last item, the current cursor is now invalid.

I’m not even sure if I can rely on this observation but I’m wondering how people implement cursor styled pagination where in the cursor might get invalidated due to mutations. Is there a pattern in apollo which can help this? Is there a good alternative than fetching everything again?

Thanks!


Solution

  • I would advise you to use a standardized approach - GraphQL Cursor Connections Specification.

    The basic idea is to query a certain number of elements after a certain cursor or before a certain cursor. With this approach, changing the set of returned elements will not affect the result of the next query.

    Example query from the specification:

    {
      user {
        id
        name
        friends(first: 10, after: "opaqueCursor") {
          edges {
            cursor
            node {
              id
              name
            }
          }
          pageInfo {
            hasNextPage
          }
        }
      }
    }
    

    Note the presence of an object in the response called PageInfo which describes the start and end cursors in the returned selection and whether there are pages after and before them.

    type PageInfo {
      endCursor: String
      hasNextPage: Boolean!
      hasPreviousPage: Boolean!
      startCursor: String
    }
    

    P.S. Typically, element identifiers (sometimes base64 encoded) can be chosen as cursors.