`cache.evict` does not cause the query hook to return `{ data: undefined }` again — the hook still returns cached data

Let’s say I have a useQuery with the following fetch policies:

const { loading, data } = useQuery(GET_CATEGORIES, {
      fetchPolicy: "cache-and-network",
      nextFetchPolicy: "cache-first",

Somewhere else in the code I remove the query’s only queried field with cache.evict:

cache.evict({ fieldName: "categories" });

Because I’m deleting the entry from the cache, I expect the hook to trigger a render with { data: undefined }, but this is not what happens.

  • If I leave the code as-as, the query is silently refetched without re-render (unless the server returns different data).
  • If I add notifyOnNetworkStatusChange: true, then loading will be set to true but data will still be defined.

I also tried the following ways of evicting the data:

// Behaves the same way as the `cache.evict` call above:
  fields: { categories: (_, { DELETE }) => DELETE },

// Behaves the same way, except I get `networkStatus: NetworkStatus.refetch`
// instead of `loading: true`
  updateCache(cache) {
    cache.evict({ fieldName: "categories" });

And I’ve read the following threads on this topic:

My questions are:

  1. Is this intended behavior or is this a bug?
  2. How can achieve what I want — delete data from the cache and have data set to undefined until it’s fetched again from the network?

Link to play around with this issue:

1 Like

Hi! I have similar problem (there). Please tag me if you find a solution

I can’t tell whether it’s intended, but it’s kinda what I would expect. Imo it doesn’t make sense to have an active useQuery hook but neither a network request nor data in the cache.
You say “until it’s fetched again from the network” - but when would that happen? The behaviour you describe, “the query is silently refetched [and re-renders when the server returns different data]”, makes total sense to me.

Or did I misundertstand, and you do expect the network request to happen (due to the cache being empty), but you want the hook to return { data: undefined, loading: true } only while the network request is happening?

This is part of it, yes. I believe { data: undefined } should be returned until the cache is repopulated, either as a result of a networks request completing, or by other means.

I’ll try my best to clarify why I believe that what I proposed makes more sense.

Let’s say instead of evict I do cache.modify and change a value of a field. Doing this would cause all hooks that use this value to re-render their components with the updated value. This gives me the idea that my components are reactively in sync with the cache. Same happens when adding a new value to cache.

So, “create” and “update” operations cause reactive updates across all hook uses, while the “delete” operation is “special” and does not, hence the inconsistency.

To look at this from another angle, let’s add a forced remount of the component that uses useQuery with the deleted value:

const [key, setKey] = useState(0)
// ..in a click handler...
  cache.evict({ fieldName: "categories" });
  // (We assume that 20ms is not enough for the next network request to complete)
  setTimeout(() => setKey(Math.random()), 20);
// ...
return <CategoriesPage key={key} />

What currently happens after we click the button is following: when the new element is mounted, data will be undefined because it was deleted from the cache (which is as we expect). But the data was deleted before the remount and did not cause an immediate update until the timer ran out. So, between the button click and the end of the remount, nothing has changed in the app state, however, the app still renders differently. This may be seen as confusing, as it indicates that Apollo not only stores data is cache, but also uses some local state in its hooks to store copies (or references) of values.

My 3rd point is that this indicates a gap in Apollo Client’s functionality. There seems to be no way of getting the app (or a component) back to the state when it was mounted for the very 1st time (when the cache was empty). If I want the component to behave as if it’s rendered for the first time, I am forced to remount it (which is not ideal for obvious reasons) instead of simply calling some Apollo’s cache API that would delete a cached value and broadcast the change.

I hope this provided some more context!

Ah, yes that would make sense. If the cache is empty and data is currently being fetched, the hook should return { data: undefined, loading: true } to indicate that.
Possibly it won’t do that if you haven’t turned on notifyOnNetworkStatusChange: true (which is generally necessary to cause a rerender while data is re-fetched IIRC), but you say it doesn’t work even with that. The data that was evicted from the cache should not be returned as data but as prevData at best.

1 Like