Grouping keyArgs for pagination when using before and after cursor

Hello,
I have run into an issue with caching when it comes to my pagination.
my query accepts this args

query CatalogedPaints(
  $brandId: String
  $categoryId: String
  $color: PaintColor
  $search: String
  $after: String
  $before: String
  $perPage: Int
  $sortByColumn: SortField!
)  {
  catalogedPaints(
    brandId: $brandId
    categoryId: $categoryId
    color: $color
    search: $search
    after: $after
    before: $before
    perPage: $perPage
    sortByColumn: $sortByColumn
  ) {
      ... fields 
     }

now i started with specifing keyArgs that are used for filtering

keyArgs: [
    'brandId',
     'categoryId',
     'color',
     'search',
     'sortByColumn',
],

this is working great, when for example sortByColumn changes query is cached in a separate entry, and i can get next page without an issue.

But problem is that my query uses cursor to specify sorting order, so after means desc order, and before is asc order. They can be undefined, null (initial sorting in given direction), have a string value = encoded cursor.
Now, problem is if it’s possible to have two separate cache entries, one for using after and other for using before. I cannot specify both in keyArgs since this would cache each cursor separately.

I was almost able to solve this, by doing

const graphcache = new InMemoryCache({
    typePolicies: {
        Query: {
            fields: {
                catalogedPaints: {
                    keyArgs: (args) => {
                        if (args?.after !== undefined || !args?.before) {
                            return [
                                'brandId',
                                'categoryId',
                                'color',
                                'search',
                                'sortByColumn',
                                'before',
                            ];
                        }

                        return [
                            'brandId',
                            'categoryId',
                            'color',
                            'search',
                            'sortByColumn',
                            'after',
                        ];
                    },
                    merge(existing, incoming) {
                        return paginationMerge(existing, incoming);
                    },
                },
            },
        },
    },
});

and

const paginationMerge = (existing: any, incoming: any) => {
    if (!existing) {
        return incoming;
    }

    const mergedNodes = [...(existing?.nodes || [])];

    incoming?.nodes?.forEach((element: Reference) => {
        if (!mergedNodes.some(({ __ref }) => __ref === element.__ref)) {
            mergedNodes.push(element);
        }
    });

    const result = {
        ...existing,
        ...incoming,
        nodes: mergedNodes,
    };

    return result;
};

but this only gives me

  • Can paginate and merge results using after
  • Can switch from after to before and see initial before results
  • Can switch from before to after and see previously cached results

what im missing

  • Merging results when using before. I have results for initial before: null, but then when i try to get next page with before: ‘someValue’ existing field shows me results that where cached when using after

Is there a way to make this work? Or do I need to have a separate argument for order direction instead of after/before?

Hey @ookil :waving_hand:

Have you considered using a custom read function? read functions have access to all args on the field, so you might be able to implement the logic needed for the cursor/sorting in here. You can have your read function return undefined if your logic detects that it doesn’t have enough data to fulfill the data requirements and this will cause Apollo Client to fetch that query from the server.

Start by tinkering with a read function here to see if you’re able to remove that before/after cursor from keyArgs. Hope that helps!