Apollo-client: Restore API does not retain ROOT_QUERY, ROOT_MUTATION

Created on 13 Nov 2020  路  6Comments  路  Source: apollographql/apollo-client

Intended outcome:

Calling cache.gc() after the cache is restored using a tool like https://github.com/apollographql/apollo-cache-persist should not remove all entities in the cache and only remove unreachable entities.

Actual outcome:

Calling cache.gc() after the cache is restored using a tool like https://github.com/apollographql/apollo-cache-persist removes all entities in the cache.

Explanation

It looks like this happens because when you call cache.restore(data) as defined here: https://github.com/apollographql/apollo-client/blob/master/src/cache/inmemory/inMemoryCache.ts#L99

and subsequently call EntityStore replace: https://github.com/apollographql/apollo-client/blob/master/src/cache/inmemory/entityStore.ts#L238

it hydrates the cache, but never calls retain on any ID. Normally, the ROOT_QUERY is retained by a write to the store: https://github.com/apollographql/apollo-client/blob/master/src/cache/inmemory/writeToStore.ts#L114:L114

but if nothing has been written to the store yet, and gc is called immediately after a cache.restore using a tool like cache-persist, then there are no rootIds retained and the entire cache will get garbage collected.

I think the solution would be to either:

  1. Default the rootIds to include ROOT_QUERY and ROOT_MUTATION on cache instantiation so that they are not lazily retained on a call to writeToStore but assumed to be desired to be retained by default
  2. Retain ROOT_QUERY and ROOT_MUTATION if a call to cache.restore includes those keys at the time restore is called
  3. Have downstream tools like cache-persist know that after doing a restore, they will need to retain IDs they care about like ROOT_QUERY and ROOT_MUTATION. I think this is a riskier approach, because as a user I would assume that ROOT_QUERY should be retained by default based on the documentation of cache.gc():

Screen Shot 2020-11-13 at 11 26 21 AM

I think rootIds is not a familiar term to most folks in these docs and they'd assume that unreachable would mean that there are things like dangling normalized entities, not potentially the ROOT_QUERY itself.

Let me know what your thoughts are, thanks!

Most helpful comment

@benjamn Genius, I was indeed importing getDataFromTree from @apollo/react-ssr. I switched the import to @apollo/client/react/ssr and it's working properly with newer versions (now 3.3.6)

Thanks so much and sorry for the distraction!

All 6 comments

@danReynolds Thanks for flagging this serious problem, and for writing it up so thoroughly!

I've released a partial fix (#7333) in @apollo/[email protected], with a more complete fix coming soon in v3.3.0 (#7334). The full fix seemed a little too risky for a patch release, but the v3.3 release should be coming any day now. Please let me know if you have any questions or feedback on either part of this solution.

Is this still an issue in 3.3.0 ? An app I'm working on has an empty SSR initial cache (client.cache.extract() ) when upgrading from 3.2.5 to any more recent version. We're sticking to 3.2.5 for now, I think there are still issues related to this in 3.3.0.

@loteoo Are you saying SSR sends down non-empty data that you restore with client.cache.restore(data), but client.cache.extract() later gives you an empty object (after cache.gc()), or do you mean SSR isn't generating any data?

@benjamn I think SSR isn't generating data? Or maybe that data can't be extracted via client.cache.extract() anymore 馃 . Sorry I'm not sure, but here's an example with more details:

let state = null;
try {
    await getDataFromTree(
        <ApolloProvider client={client}>
            <App {...props} />
        </ApolloProvider>
    );
    state = client.cache.extract();
} catch (error) {
    console.error('Error while running `getDataFromTree`', error);
}

console.log(state);
// In 3.2.5, state is populated with data from queries
// In 3.2.6 and above, it's an empty object

Sorry if I'm not sure which is the culprit here (cache.gc() or client.cache.extract() or something else ) 馃檹 .

I am fairly certain it has to do with @apollo/[email protected] and above tho 馃 .

Maybe there's an issue with the data being cleared in getDataFromTree? Should I create a seperate issue?

@loteoo If you're not calling cache.gc() in that code, this issue (#7323) probably isn't the cause of your problems, but getting an empty object is definitely strange/worrisome!

A couple more questions:

  1. Are you importing getDataFromTree from @apollo/client/react/ssr?
  2. Are you importing ApolloProvider from @apollo/client (or @apollo/client/react/context)?

The older @apollo/react-* packages should no longer be used.

@benjamn Genius, I was indeed importing getDataFromTree from @apollo/react-ssr. I switched the import to @apollo/client/react/ssr and it's working properly with newer versions (now 3.3.6)

Thanks so much and sorry for the distraction!

Was this page helpful?
0 / 5 - 0 ratings