-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Discussion] fetchMore is a bad match with cache redirects (type policies) #6394
Comments
I'm sure what you're describing makes perfect sense to you, given your current understanding of the design space, but it would really help if you could put together a couple of concrete reproductions, so we can move past generalizations and get into the details. |
@benjamn fine, here is the repro: https://github.com/darkbasic/apollo-gh6394 |
@benjamn please refetch, I forgot to add the logic to update the source query in fetchMore. |
Basically I want type policies to handle these things:
Right now type policies handle the first half of the list but not the second half and that creates a lot of confusion. If they handle redirects they should also handle insertions/deletions: the source and target queries must be kept in sync by some custom defined logic. |
Responding to this comment from the reproduction:
@darkbasic You absolutely should omit pagination control arguments from your I would even suggest using This approach will make the |
@benjamn I would really love to, but I'm still facing several shortcomings with the appoach described in https://www.apollographql.com/docs/react/v3.0-beta/caching/cache-field-behavior/#handling-pagination The second example shows some kind of super-simplified one-way cursor base pagination, but:
Do you have any example of Relay-style pagination (forward and backward) with read/write functions? |
I'm going to work on some helper functions to generate field policies for pagination today, and I will make sure they cover these use cases. As I mentioned over in #5951 (comment), |
@benjamn thanks! I personally don't care about breaking changes at this stage of AC3 development, for what concerns me take your freedom to make the API as pleasant as possible to work with. Btw any news about exposing args to cache.modify? It's something I really need in order to be able to finally modify queries with filters. |
@darkbasic Have a look at #6465 for my take on Relay-style pagination via field policies. While the I still think it's a good idea to provide |
@darkbasic I've been thinking more about For any field that you plan to modify with new InMemoryCache({
typePolicies: {
ParentType: {
fields: {
someField: {
read(existing) {
// Since the read function ignores existing.args and just returns existing.value,
// the args are effectively hidden from normal code that reads from the cache.
return existing?.value;
},
merge(existing, incoming, { args }) {
return {
// You could adjust/filter the args here, or combine them with existing.args,
// or whatever makes sense for this field.
args,
value: doTheMerge(existing?.value, incoming, args),
};
},
},
},
},
},
}) If this becomes a common pattern, you could wrap it up in a helper function: new InMemoryCache({
typePolicies: {
ParentType: {
fields: {
someField: fieldWithArgs((existingValue, incomingValue, args) => ...),
},
},
},
})
function fieldWithArgs<TValue>(
doTheMerge: (
existing: TValue,
incoming: TValue,
args: Record<string, any>,
) => TValue,
): FieldPolicy<{
args: Record<string, any>;
value: TValue;
}> {
return {
read(existing) {
return existing?.value;
},
merge(existing, incoming, { args }) {
return {
args,
value: doTheMerge(existing?.value, incoming, args),
};
},
};
} Later, when you call cache.modify({
id: cache.identify({ __typename: "ParentType", parentId }),
fields: {
someField({ args, value }) {
// Examine args to help with processing value...
return { args: newArgs, value: newValue };
},
},
}) I honestly think this workaround gives you more power to get things right than any automated |
@benjamn it's great to have something like this baked in the core. Although an offset/limit pagination is not as complex as the relay style, I think it'd be nice to also have it baked in, or at least some helpers. It took me time to come up with what I commented end up writing here #5881 (comment) |
@benjamn I just had to do exactly that, I needed to access the args in order to update the cache. Returning them from the merge function works perfectly for that use case. |
by the way is the is the |
@tafelito Yes, |
OK got it. And is there anyway to know from the read what |
@benjamn sorry to keep iterating over this but you said that using |
One thing I noticed is that for the reason why the merge is not called, and I'm still trying to figure it out by debugging it, is that the data that comes from the fetch is somehow cached by AC somewhere. As you can see the totalCount is 3 and the nodes are 3 objects as well. But then, when I debug it, in this function the result always has totalCount 2 and 2 items in the nodes array. And because the data is the same as the previous, then the merge is never called. Is there anything else I can try to try to figure this out? |
I still haven't seen someone make a compelling case that storing last args would do more harm than help. Having last args wouldn't prevent you from having "enough power to get things right" by employing more complicated schemes. Y'all haven't really explained why you're actually averse to storing last args. Not wanting to add more API surface area? If someone needs a bizarre use case like looking back through the history of args on a field, let them go to extra trouble to implement that, instead of going to extra trouble for all the common cases that would be solved by having access to the last args (which are inherently tied to the last query results). To me the balance between ease of use and power user features is taking a nose dive in the power user direction. |
For me a more systematic solution to pagination, if the cache isn't going to store last args, would be to make my generic server-side pagination resolver echo the search/sort/pagination args back alongside the list in the query result. At least then I wouldn't have to specifically add this workaround to field policies anytime I create a new paginated field. |
As outlined in a previous comment (#6350 (comment)) cache redirects can be annoying because when you use cache.modify you will have to remember to update the fields which feed the type policy instead of the ones you're actually working on.
Another thing worth mentioning in the docs is that
fetchMore
doesn't also play well with cache redirects: it will write a new query to the cache with the new elements appended. Probably you want to keep both queries (the source one used in type policies and the target one) in sync with the latest changes, so you'll have to remember to manually update the source one in theupdateQuery
method offetchMore
.How to improve the current state of the things? In a previous comment (#6289 (comment)) I suggested that type policies seem half baked and that would be nice to be able to use them the other way around as well: instead of having to use the
update
method, whenever I insert a new item or remove an existing one I would love to be able to define a policy for that, which will be in charge to update the rest of the cache. That would resolve all of the previous issues and would also help to decouple the cache updating logic to the specific components (which shouldn't have knowledge of the rest of the application).Unfortunately I don't know how to turn this into a GitHub Discussion.
The text was updated successfully, but these errors were encountered: