ferry icon indicating copy to clipboard operation
ferry copied to clipboard

Cache not updated after adding paginated data through updateResult with ferry

Open rostaingc opened this issue 1 year ago • 4 comments

I have a paginated list of photos (by groups of 50) that I fetch via a graphql query through ferry:

query Photos(
  $paging: CursorPaging!
) {
  photos(paging: $paging) {
    edges {
      cursor
      node {
        id
        name
      }
    }
    pageInfo {
      startCursor
      endCursor
      hasNextPage
    }
    totalCount
  }
}

I've set a requestID such as 'AllMyPhotos' not dependant on the cursor and have checked that that requestID is used everywhere.

When I want to load more data, I use the updateResult function:

fetchMore: (_) {
        if (data == null) return;
        final endCursor = data!.photos.pageInfo.endCursor?.value;
        if (endCursor == null) return;
        final client = locator<Client>();
        final request = ref.read(photosReqProvider);
        client.requestController.add(request.rebuild((req) {
          req.vars.paging.after.value = endCursor;
          req.updateResult = combinePhotos;
        }));
      },
GPhotosData? combinePhotos(GPhotosData? prev, GPhotosData? next) {
  if (prev == null) return next;
  return next?.rebuild((data) {
    data.photos.edges.insertAll(0, prev.photos.edges);
  });
}

I manage to get all the data in data.photos correctly.

But when I read the cache I only have the first 50 items inside and not all the items I have loaded so far.

final cache = locator<Client>().cache;
final photosData = cache.readQuery(photosReq);
if (photosData == null) return;

final photoIDs = photosData.projectPhotos.edges.map((edge) => edge.node.id).toList();
// photosIDs is always 50, even when the data previously contained 100,150, etc when fetchingMore

Does updateResult not update the cache for that requestID ?

rostaingc avatar May 11 '23 02:05 rostaingc

Looking at the code, I think the issue comes from the FetchPolicyTypedLink that calls _writeToCache/cache.writeQuery which calls normalizeOperation but never passes the requestId.

@knaeckeKami do you think it'd be ok to pass the requestId in the data of resolveDataId? Then we could use the typePolicies to group the requests together.

GP4cK avatar May 12 '23 08:05 GP4cK

The cache will cache all queries, but it will cache them together with the arguments for paging, and if you read only the first page, it will only return the first page.

But you can add configuration to merge all pages together, like described here:

https://ferrygraphql.com/docs/cache-configuration#the-fields-property

knaeckeKami avatar May 12 '23 08:05 knaeckeKami

In my use case, I have a table view and i can go to the first page, next page, previous page and last page, also I can apply filters and sorting.

So based on this documentation, I should be setting my ['filter', 'sorting'] as keyArgs as I do not want results of queries with different filters/sorting to be merged together.

How to handle the fact that the user may navigate to the first page, then the last page, then press previous etc. All the results will be added in some random order to my cache ?

Also I will need to not only merge the edges together in my result but also compute the pageInfo to get the startCursor, endCursor and hasNextPage.

query ProjectPhotos(
  $filter: ProjectPhotoFilter!
  $paging: CursorPaging!
  $sorting: [ProjectPhotoSort!]
) {
  projectPhotos(filter: $filter, paging: $paging, sorting: $sorting) {
    edges {
      node {
        id
        name
      }
    }
    pageInfo {
      startCursor
      endCursor
      hasNextPage
    }
    totalCount
  }
}

    'Query': TypePolicy(
      fields: {
        'projectPhotos': FieldPolicy(
          keyArgs: const ['filter', 'sorting'],
          merge: (existing, incoming, option) {
            if (existing == null) return incoming;
            final existingEdges = (existing['edges'] as List<dynamic>?) ?? [];
            final incomingEdges = (incoming['edges'] as List<dynamic>?) ?? [];
            final mergedEdges = (LinkedHashSet<dynamic>(equals: jsonMapEquals, hashCode: const DeepCollectionEquality().hash)
                  ..addAll(existingEdges)
                  ..addAll(incomingEdges))
                .toList();
            return incoming['edges'] = mergedEdges;
          },
        ),
      },
    ),

rostaingc avatar May 16 '23 03:05 rostaingc

For this, you will have to use the 'read' parameter on the FieldPolicy in order to execute your query based on the variables passed to your query.

knaeckeKami avatar May 16 '23 07:05 knaeckeKami