Hey,
I’ve been trying to figure this out for a few days now and the further I get with understanding caching, pagination, and Type Policies w/ Apollo Client the deeper of a hole I find myself. The package is great, I’m sure the problem lies in my lack of understanding. Help is greatly appreciated in educating me on the best method of implementation or practice here. Sorry before hand if this is not the right place or format.
Basically we have backend pagination on our server and we are currently using offset, limit, order (by, desc). On initial load I get my limit 10, offset 0 with an order by name
field with the result also returning totalItems value, let’s say 20.
In order to improve performance I attempt to handle pagination, as well as delete and adding items all through AC3 caching with various updates, cache.modify and Type Policy handling detailed below:
// type-policy
NamespaceDocumentQuery: {
fields: {
get: {
...offsetLimitPagination({
field: 'documents',
}),
},
},
},
Below is my custom offsetLimitPagination
// A basic field policy that uses options.args.{offset,limit} to splice
// the incoming data into the existing array. If your arguments are called
// something different (like args.{start,count}), feel free to copy/paste
// this implementation and make the appropriate changes.
export const offsetLimitPagination = (props?: IPagination): FieldPolicy => {
const { keyArgs = false, field } = props || {}
return {
keyArgs,
read(existing, options) {
console.log('options?.args', options?.args)
console.log('existing', existing)
const { offset, limit, order } = parseArgs(options?.args)
const { canRead, readField } = options
if (offset < 0 && limit < 0) {
return { ...existing, args: options?.args }
}
const items =
existing?.[field]?.length && offset >= 0 && limit >= 0
? existing?.[field].filter(canRead)
: existing?.[field]
// sort items
const sortedItems = items.slice(0).sort((a, b) => {
return sortByStringField(order?.[0]?.by, order?.[0]?.desc)(
readField(order?.[0]?.by, a),
readField(order?.[0]?.by, b),
)
})
const page = sortedItems?.length ? sortedItems.slice(offset, offset + limit) : sortedItems
const itemsPerPage = limit || 0
const totalItems =
existing?.paginationInfo?.totalItems - (existing?.[field]?.length - sortedItems?.length) ||
0
const totalPages = Math.ceil(totalItems / limit)
// A read function should always return undefined if existing is
// undefined. Returning undefined signals that the field is
// missing from the cache, which instructs Apollo Client to
// fetch its value from your GraphQL server.
if (page?.length < itemsPerPage && sortedItems?.length < totalItems) {
return undefined
}
return {
...existing,
documents: page,
paginationInfo: {
...existing?.paginationInfo,
offset,
limit,
totalItems,
totalPages,
},
}
},
merge(existing, incoming, options) {
// set merged object with empty array
const merged = {
[field]: [],
// for non related array just return incoming
...incoming,
}
// set existing data if it exists
// Slicing is necessary because the existing data is
// immutable, and frozen in development.
merged[field] = existing ? existing[field].slice(0) : []
// perform operations
const { offset = 0 } = parseArgs(options?.args)
// offset
offsetData(merged, incoming, field, offset)
return {
...merged,
order: options?.args?.org,
}
},
}
}
I have a merge to handle offset, and read to handle limiting, and sorting (will look to handle filtering as well). I am using a useLazyQuery
to fetch data when paginating.
const [getData, documentRes]: any = useDocumentGetLazyQuery({
notifyOnNetworkStatusChange: true,
})
When I go to nextPage, and the getData is called the result is cached and in our query we ask for paginationInfo which maintains the limit and offset, as this changes the cache is refreshed and the UI is updated. So at this point limit, offset is all working well.
My first issue is when I do an initial load and sort. Because the sort is not returned in the query result there is no update to the cache, so when I sort because I’ve only received the first 10 records out of 20 it would only sort my first page data and not my entire data set. Thus I need to apollo cache know that this order
field is a keyArgs
(right?). I add this to my typePolicy and when sorting cache is updated adding two separate entries for based on the new keyArg
so I would have two entries. I can live with that, the problem becomes when I try and delete or add items to this list.
I use update
and cache.modify
which looks something like this:
update: (cache, { data }) => {
// evict before modifying
cache.evict({
id: `Document:${id}`,
})
cache.gc()
cache.modify({
fields: {
document: (existingDocumentsRef, { readField }) => {
const getRef = readField<DocumentGetPayload>({
fieldName: 'get',
args: {
input: {
...createGetRefArgs({
companyId,
paginationInfo,
}),
},
},
variables: {
input: {
// filter: {
// companyIds: [companyId],
// },
order: {
by: sorting?.sortState?.backendSortKey,
desc: sorting?.sortState?.sortDirection,
},
},
},
from: existingDocumentsRef,
})
console.log('getRef ***', getRef)
console.log('on delete result', {
...existingDocumentsRef,
get: {
...getRef,
documents: getRef?.documents?.filter((ref) => readField('id', ref) !== id),
paginationInfo: {
...getRef?.paginationInfo,
totalItems: getRef?.paginationInfo?.totalItems - 1,
},
},
})
return {
...existingDocumentsRef,
get: {
...getRef,
documents: getRef?.documents?.filter((ref) => readField('id', ref) !== id),
paginationInfo: {
...getRef?.paginationInfo,
totalItems: getRef?.paginationInfo?.totalItems - 1,
},
},
}
},
},
optimistic: true,
})
},
The main problem here is that know my refKey is not just a string but has the my args attached to it in the form of key({input:{order})
and in the return result of my cache.modify
I do not know how to return or update that same ref with the args attached? So in this case it will create a new cache without the args and not update the correct reference.
- In my initial issue with the sorting when
keyArgs: false
what’s the best approach to handling sorting? Can I refetch the data from server at some point. How can I trigger this should I use the newrefetchQuery
method when clicking on sort - When
keyArgs: ['order']
How can I return the correct cache reference with the args attached in mycache.modify
to ensure the correct cache reference is being updated - What is recommended overall when sorting comes into play with your pagination?
I will try and provide a reproducible repo or sandbox. Thanks ahead of time for your support