Refetch should not trigger a field merge
See original GitHub issueIntended outcome:
From the docs for refetch:
Update the variables of this observable query, and fetch the new results
It seems wrong to me that when the new results come back it triggers the field’s merge function. As we are resetting the variables the previous value for the query should be considered invalid and thrown out implicitly.
If there is not agreement on the above proposition, at the very least I would like to have a way to know that I’m in a merge function as the result of a refetch, so I can throw out the previous result explicitly.
Actual outcome:
The field’s merge function is triggered and new results are merged with the old, creating an erroneous cache entry that results from a combination of old variable values and new.
How to reproduce the issue:
This is default behavior, trigger a refetch on a query whose result has a custom field merge policy. The problem is most easily identified in the context of list type fields and pagination.
Versions
System:
OS: macOS 10.15.7
Binaries:
Node: 12.18.2 - ~/.nvm/versions/node/v12.18.2/bin/node
npm: 6.14.5 - ~/.nvm/versions/node/v12.18.2/bin/npm
Browsers:
Chrome: 87.0.4280.88
Firefox: 79.0
Safari: 14.0.2
npmPackages:
@apollo/client: 3.0.0-rc.4 => 3.0.0-rc.4
apollo-angular: ^2.0.0-beta.2 => 2.0.0-beta.2
Issue Analytics
- State:
- Created 3 years ago
- Reactions:14
- Comments:13 (4 by maintainers)
Top GitHub Comments
Here’s my current thinking on the original issue, as reported by @vigie and echoed by @JeffJankowski:
If you define a
merge
function for a field,InMemoryCache
must call that function any time new data is written for that field, even the very first time (when theexisting
data is undefined), becausemerge
functions give you the ability to customize the internal representation of field data within the cache. Ifmerge
was called most of the time but not all the time, it would be very difficult to know what type of data to expect when reading from the cache.However, I believe we can preserve this guarantee (that
merge
is always called) while achieving the behavior @vigie is proposing, by providing a way to callmerge
functions with undefinedexisting
data, so they behave exactly as they would if there was no existing data, and thus do not attempt to combine incoming data with existing data (but still potentially transform theincoming
data, as appropriate).My current plan is to add an
overwrite?: boolean
option to thecache.writeQuery
andcache.writeFragment
option types. It will befalse
by default, but passingoverwrite: true
will prevent anymerge
functions involved in the write from seeing any existing data, thereby overwriting those field values. For fields that do not have amerge
function, new field data always replaces existing data, so you can think ofoverwrite: true
as a way to achieve that default behavior even when you have amerge
function.Of course, since you don’t manually call
cache.writeQuery
when you’re usingrefetch
, we will need to arrange foroverwrite: true
to be passed behind the scenes, whenrefetch
does its cache write. I agree this should be the default behavior forrefetch
, but we may need to make it optional (opt-in) to avoid breaking changes.I welcome your thoughts/questions about this idea. It is straightforward to implement, but needs more unit tests. I’ll kick off a PR tomorrow, with the goal of releasing it in Apollo Client 3.4 (and in the next beta release, more immediately).
The issue @jtoce identified in https://github.com/apollographql/apollo-client/issues/7491#issuecomment-767985363 may well be a contributing factor (preventing writes in some cases, thereby not triggering any
merge
functions), but that should be easy to fix by deletingqueryInfo.lastWrite
before refetching. I’ll put that change in the PR too.Really need this to be fixed, as I can’t both use
fetchMore
andrefetch
while migrating from Apollo Client 2 to 3,refetch
being broken (themerge
logic doesn’t have the info if the cache write is a pagination or a refetch). 😅 🙏