Hi,
Disclaimer: I’m new to GraphQL so I’m looking for advice from experts.
In my React application, I do the following:
- Retrieve some variations of a
Person
object all over the application, through slightly different queries.
- The object always contains a list of
Tags
- In one component, I create a new
Tag
and apply it via a mutation to a specific Person
. Now the Person
, in the back-end contains an extra tag in its Tags list.
In details:
-
Home page
: queryPersonsWithSomeTags
-
Home page
: queryAllTags
-
Person page
: queryPersonWithAllTags
-
Another page
: queryTagsThatBelongsToListOfPersons
-
Another page
: mutationCreateTag
-
Another page
: mutationApplyTagToListOfPersons
InAnother page
, I use the mutation’s mutationApplyTagToListOfPersons
“update” function to update Apollo’s cache but that is not scalable as I need to update the cache of every single query that returned the tag object.
Is updating the cache of various queries expected or it is most likely a misuse of the library or a mis-design of the application/queries where this case should not happen?
Thanks for the help and for the great library!
Nico
Apollo’s InMemoryCache is normalized, which means that even if you request the same Person
multiple times but with different fields across your application, in all of these cases the same Person
object will be referenced in the cache. This means that if you fire off a mutation that changes a field on this particular Person
(tags
in your example), you will have to perform only a single cache update:
mutationApplyTagToListOfPersons({
update: (cache, { data }) => {
cache.modify({
id: 'the cache id of your Person object you wish to modify',
fields: {
tags: (existingTags, { toReference }) => (
[...existingTags, toReference(data.mutationApplyTagToListOfPersons)]
)
}
});
}
});
Hello! Hope you don’t mind me chiming in.
Is there any reason why the normalization would fail?
In one section of my app, shipments
can be marked as hidden or not (isHidden
). The shipments appear in a list that can be filtered and sorted, and of course manually modifying the cache for each individual combination of filters and sort fields would be unsustainable.
I have a custom mutation toggleHide
which accepts one or several shipments and flips the isHidden
value.
The following handler runs after the mutation. Individual shipments reflect the updated cache correctly. But as soon as I change the filters or sort fields I am seeing stale cache. I am not seeing any console errors anywhere.
const handleAfterToggleHide = function (cache, payload) {
payload.data.toggleHide.map((modifiedShipment) =>
cache.modify({
id: cache.identify(modifiedShipment),
fields: {
// Toggle value of isHidden to its opposite
isHidden(cachedValue) {
return !cachedValue;
},
},
})
);
};
Any idea why this would work for shipments that are currently visible on the screen, but not work for any other queries?
The only way I can imagine normalization not to work in this scenario, is if each permutation of filters stores different Shipment
objects. Not sure how that can happen without seeing more of your code (what do the graphql queries look like?) and/or the contents of your cache. For the latter, you can check out this Chrome extension and see whether or not the same Shipment
objects are referenced throughout the cache.
The query to list the shipments is pretty basic, see below. I see what you are saying about each permutation being different but I agree that sounds very unlikely, unless I am missing something here.
export const ALL_SHIPMENTS_QUERY = gql`
query ALL_SHIPMENTS_QUERY(
$orderBy: String
$showHidden: Boolean
$userId: ID!
) {
allShipments(
orderBy: $orderBy
where: {
createdBy: { id: $userId }
{ isHidden: $showHidden }
}
) {
id
isHidden
createdBy {
name
}
// ... more fields
}
The only thing that occurs to me is that the custom mutation messes this up somehow, but I don’t see how, since I’m able to identify the cached object correctly.
This is the mutation (frontend):
mutation TOGGLE_HIDE_MUTATION($shipmentIds: [ID!]) {
toggleHide(shipmentIds: $shipmentIds) {
id
}
}
The mutation is executed in a Keystone custom mutation resolver and this is the return value:
return await context.lists.Shipment.updateMany({
data: updatedShipments, // shipments with .isHidden updated
resolveFields: graphql`
id
`,
});
Does anything jump out to you at all?
The one thing that does jump out to me is that your isHidden
field does not seem to be referenced anywhere. You’re modifying it with your cache.modify
calls, but it’s present in neither your query nor your mutation. If that’s the case, I would actually expect your cache.modify
calls to not do anything at all because they can only modify fields that are already in the cache - and if your query never fetched isHidden
, it won’t be in the cache. I find it odd that you’re noticing changes in your shipments at all.
That said, if you did reference isHidden
in both your query and your mutation, Apollo should be able to perform the cache updates automatically (making your cache.modify
calls unnecessary), because the mutation will return a list of objects containing id
and __typename
fields which are already in the cache.
Hello! I have updated my ALL_SHIPMENTS_QUERY
above, which was previously edited for brevity. isHidden
is one of the fields I am selecting, and it is also part of the where
clause.
However I don’t reference isHidden in the mutation, instead I just pass the ID as argument and toggle the field inside a mutation resolver in the backend. For this reason I see why cache.modify is necessary, since the field is changed without Apollo’s knowledge.
Is it possible that I have to use a non-custom mutation for cache to work? I’m not sure why, since I already am able to use cache.identify
+ cache.modify
and it’s partially working.