Intended outcome:
Given that we cache some data with a query like this:
query AllData {
computer {
id
name
cpu {
model
clockSpeed
}
}
}
and later in the program run a query like this (with cache-and-network or network-only fetch policy):
query ClockSpeed {
computer {
id
cpu {
clockSpeed
}
}
}
the cached cpu object should have both model and clockSpeed fields.
Actual outcome:
Only clockSpeed remains in the cache.
How to reproduce the issue:
Here is a reproduction of the issue: https://github.com/od1k/apollo-client-issue-2
Checkout branch 3.0 and start the app.
Model button - cached value gets displayed.Clock speed button.Model again - value is undefined. It is gone from the cache.Branch 2.6 contains the same implementation that works as expected with Apollo Client 2.6.
As a workaround, it is possible to add an ID field to the nested type. That way it gets normalized in the cache and the query results ar merged as expected.
Versions
System:
OS: macOS 10.15.1
Binaries:
Node: 11.10.1 - /usr/local/bin/node
Yarn: 1.19.1 - /usr/local/bin/yarn
npm: 6.13.4 - /usr/local/bin/npm
Browsers:
Chrome: 79.0.3945.88
Firefox: 59.0.1
Safari: 13.0.3
npmPackages:
@apollo/client: ^3.0.0-beta.19 => 3.0.0-beta.19
This is a consequence of https://github.com/apollographql/apollo-client/pull/5603. In short, because the ProcessorType object does not have an ID, the cache cannot tell when two ProcessorType objects have the same identity, so it can never safely merge their fields, and must instead replace the whole object. Apollo Client 2.6 and earlier got this wrong, even though sometimes the wrong behavior may have seemed convenient. The #5603 description goes into more detail about this issue.
Of course, you (the application developer) know that the cpu of a particular ComputerType object (which does have an ID) is always logically the same ProcessorType object, so it's important to be able to tell the cache that merging computer.cpu objects is safe, provided the computer object is the same.
In AC3, you can either make sure that every ProcessorType object has an ID (as you correctly noted in your description), or you can continue to treat ProcessorType objects as unidentified data and simply define a policy for the cpu field that has a custom merge function:
const cache = new InMemoryCache({
typePolicies: {
ComputerType: {
fields: {
cpu: {
merge(existing, incoming) {
return { ...existing, ...incoming };
},
},
},
},
},
});
Before AC3 is released, we are planning to add some development-time warnings about situations when data might be lost because unidentified objects are replaced rather than merged, with links to documentation about custom merge functions. Based on my experimentation, I'm confident these warnings would have caught your problem before you noticed it, saving you some debugging.
Here's the latest draft of the AC3 cache configuration documentation: https://deploy-preview-5677--apollo-client-docs.netlify.com/docs/react/caching/cache-configuration/#configuring-the-cache
Thanks for helping test the Apollo Client 3 betas, by the way! When someone goes to the trouble of providing a simple reproduction, I'm always happy to fix it if I can (see PR linked above).
To make AC3 reproductions easier in the future, we now have an ac3 branch of the error template repository: https://github.com/apollographql/react-apollo-error-template/tree/ac3#reproduction-creation-steps
Thanks for the quick reply!
The merge function is exactly what I was looking for but I think it wasn't included in the v3 beta docs or I just missed it.