Problems merging individual pages of results into single cache store

I’m attempting to store paginated results into a single store in Apollo Client cache, as mentioned in the docs.

I’ve successfully implemented pagination with the fetchMore function. The only thing now is that nothing appears to be cached. I’m using offset and limit as input params and I’d like to merge paginated results.

My schema makes things a bit trickier than the examples in the docs as my search field accepts an input type rather than single variables and it returns another type which includes the totalCount of results and a nested array of the actual results. In addition to that the result returns a Union. This makes it difficult to specify a keyField as a unique identifier.

I have the following schema (trimmed to the main parts):

type Query {
    search(input: SearchInput): PaginatedResults
}

type PaginatedResults {
    totalCount: Int
    result: [SearchResult]
}

union SearchResult = Property | Advert

input SearchInput {
    # ...other input fields
    offset: Int
    limit: Int
}

And I have the following for InMemoryCache():

const cache = new InMemoryCache({
    typePolicies: {
        Query: {
            fields: {
                search: offsetLimitPagination(['input']),
            },
        },
        PaginatedResults: {
            keyFields: [], // No unique identifier, rely on custom keyFn
            keyArgs: ['input'],
            merge(existing = { result: [] }, incoming, { args }) {
                const offset = args?.input?.offset || 0;
                const mergedResult = existing.result.slice(0);

                for (let i = 0; i < incoming.result.length; ++i) {
                    mergedResult[offset + i] = incoming.result[i];
                }

                return { ...incoming, result: mergedResult };
            },
        },
    },
});

However, this results in an error on the frontend where I don’t receive any data. If I comment out the InMemoryCache object it sees the pagination working again but with nothing cached.

Can anyone suggest how I may amend things so that I may merge paginated results?

Thanks

The keyArgs option (also here goes inside the field policy, not inside the type policy. Only the merge function can go on type policy, to be used as the fallback for all fields that have this result type.

Specifying ['input'] for the keyArgs is not sufficient, you want to exclude the offset and limit from the SearchInput. You’ll want to use a custom function for that.
Also the offsetLimitPagination helper is not useful for you, while one can customise the keyArgs it returns one cannot customise its usage of args.offset - but the helper isn’t complicated enough to not just code this yourself.

This should work for your schema:

const cache = new InMemoryCache({
    typePolicies: {
        Query: {
            fields: {
                search: {
                    keyArgs(args) {
                        return JSON.stringify(_.omit(args.input, ['offset', 'limit']));
                    },
                    merge(existing = { result: [] }, incoming, { args }) {
                        const offset = args?.input?.offset ?? 0;
                        const merged = {
                             totalCount: incoming.totalCount ?? existing.totalCount,
                             result: existing.result.slice(0),
                        };
                        for (let i = 0; i < incoming.result.length; i++) {
                            merged.result[offset + i] = incoming.result[i];
                        }
                        return merged;
                    },
                },
            },
        },
        PaginatedResults: {
            keyFields: false,
        },
    },
});

It seems it’s also possible to specify “Nested argument names for input types with subfields for keyArgs”, in your case keyArgs: ['input', [… /*other input fields*/]], but I’m not quite sure how that works.

1 Like