Normalization does not occur on first render when cache persisted with apollo3-cache-persist
See original GitHub issueAfter I added the apollo3-persist-cache
, I’m serving stale data to users without updating it after an Apollo server response. Since all my cache policies are cache-and-netwok
I would actually expect an update on cache after I got an Apollo server response.
Intended outcome: After an Apollo server response, the Apollo client always should normalize and update the cache.
Actual outcome: I found that cache normalization is not happening every time the server responds, which it should. If I already have persisted cache when performing the first query to the server in the first render, the normalization will never happen. I checked this by passing a merge function that is never called. At this point, data will already be stale, because it comes from the user’s local cache. After this first render, the following requests, on the client side, most of the time the normalization occurs, but sometimes not.
How to reproduce the issue: Reproduce the bug here: https://github.com/igorthurow/apollo-client-bug Follow this steps:
1. After cloning, install all dependencies with `npm install`.
2. Start the development server with `npm start`.
3. Renders the first time and sees on the console that it didn't print an `@@merge function called`, that is, it didn't call the merge function. This is probably because I'm simulating data coming from the SSR and using `forceFetchDelay: 1000`
4. Remove the simulation of data coming from SSR on line 144 and reload the page.
5. See in the console that it didn't print an `@@merge function called`, that is, it still hasn't called the merge function as I expected, even though it doesn't have data coming from the SSR.
The main code have some commentaries about some points that can help to understand the issue.
Versions
System:
OS: macOS 12.0.1
Binaries:
Node: 12.10.0 - ~/.nvm/versions/node/v12.10.0/bin/node
npm: 6.10.3 - ~/.nvm/versions/node/v12.10.0/bin/npm
Browsers:
Chrome: 103.0.5060.114
Safari: 15.1
npmPackages:
@apollo/client: ^3.3.6 => 3.4.10
apollo-link-timeout: ^3.0.0 => 3.0.0
apollo3-cache-persist: ^0.14.0 => 0.14.0
Issue Analytics
- State:
- Created a year ago
- Reactions:3
- Comments:7 (4 by maintainers)
Top GitHub Comments
👋 @igorthurow - thanks for providing a reproduction. I don’t think it’s demonstrating an issue however.
Yes your
ssrForceFetchDelay: 1000
setting is the reason why themerge
function is not called. When you setssrForceFetchDelay
to anything other than 0 (and you’re using acache-and-network
fetch policy), Apollo Client toggles thefetchPolicy
internally tocache-first
for the number of milliseconds passed tossrForceFetchDelay
. So in your case you’ve preloaded the cache withPerson:4
-Person:6
, then when the query runs, it returns those people from the cache instead of attempting to run the query as that all happens before the 1000 ms mark.Keeping in mind my explanation of 3 above, when you remove the
cache.restore(ssrExtractedCache)
call from your reproduction and the page re-renders, you’re still getting back the IndexedDB stored cache data that you’ve already hydrated. This will behave exactly as it does for 3 - because of thessrForceFetchDelay: 1000
setting you’re going to get back the hydrated cache data (since Apollo Client toggles tocache-first
), and won’t hit themerge
function because the network request didn’t happen.@hwillson Hey bro! Thank you for the answer!
I understood. I figured it was something along those lines.
However, in my real scenario, I only perform a few queries in my SSR stream, and those I don’t want to repeat on the client side, because it would be duplicated and we would considerably increase the throughput, even more in such an accessed product. But we would like to use cache persistence to improve our performance and resiliency.
These points then seem to be in conflict: If I make my requests in the SSR stream I shouldn’t repeat on the client side, so I use
ssrForceFetchDelay
, but I would need to make the requests for what didn’t come from the SSR, which might have come from the cache and then they would skip. It makes sense?To me, the
ssrForceFetchDelay
seemed to be closely related to the SSR stream (including the option name), and as such, the data coming from the SSR is guaranteed to be absolutely fresh. As this option actually just skips requests as long as you have something in cache, both coming from SSR and persistence, it is not safe to use both together because certainly the data delivery will eventually be stale and will only update when we redo the requests from client side.The option I have then is either repeating the requests I just made in the SSR, on the client side, or not using cache persistence, is that it?
I was hoping to be able to make some requests in the SSR stream, merge with the cache overwriting what came from the SSR, and redo the client-side requests when they didn’t occur in the SSR stream, does that make sense? So I guarantee that I’m up to date, that I go on the network to update the cache on the first render and still get the benefit of the resiliency and performance that the cache provides.