1. 24
    Provide quick app start-up times by syncing the Apollo cache with local storage
    7m 14s

Provide quick app start-up times by syncing the Apollo cache with local storage

Rares Matei
InstructorRares Matei

Share this video with your friends

Send Tweet
Published 9 months ago
Updated 6 months ago

Whenever we start up the app, it has to wait on all its initial queries to resolve. Even after that's done, as we click around the app and load new notes in the cache, all of that data is lost on refresh, because it's kept in memory. In this lesson we'll look at using the "apollo3-cache-persist" package, to set-up a syncing mechanism that automatically saves any changes to the Apollo cache in local storage, and then restores it when the app starts. This results in quick start-up times. We'll also look at setting global "cache-and-network" fetch policies that ensure our data is kept fresh and updated with the backend. We'll also set-up a secondary "nextFetchPolicy" that reverts the global query fetch policy back to the default "cache-first" once the app bootstraps.

Instructor: [0:00] As I click for my app and I maybe switch different categories, Apollo is storing what it gets from the backend inside the local cache. We're gradually building up this graph of locally-cached entities that can be reused by components should they be ever needed again, like me clicking on the first category again.

[0:19] This cache is very useful, but all of it is kept in memory. When I refresh the app, all of it gets deleted, and we're left with my initial query again.

[0:29] This is a problem, because if I switch to the network tab -- and I'm just going to slow down my server in the background so that all of the network requests become really slow -- you'll see that if I refresh the app, I'm going to get loading spinners for a few seconds until all of my requests finish.

[0:48] This means that if I have a slow network, every time I refresh the app, I have to wait for all of my initial network requests to resolve before I can display anything useful to the user.

[1:00] Let me install the Apollo3-cache-persist package and the GraphQL annual package. If I close the terminal, back in my top level index.js file, I'll import the persist-cache and local storage wrapper utils from the Apollo3-cache-persist package.

[1:21] Remember how our Apollo cache can be accessed from the client we instantiated up here from its cache property. I am going to persist my Apollo client cache. Where do I want to persist it? Well, the storage mechanism I'll use is my browser's local storage.

[1:41] I'll need to wrap it in the local storage wrapper we imported above. That's it. Check this out. Anytime my Apollo cache is going to change, this library will write those changes to local storage as well. You'll also notice this returns a promise because I can call .Dan on it.

[2:01] The second thing this utility does is that the first time my app launches, its going to look inside local storage. If there's any Apollo info saved in there, it's going to restore it back to my in-memory cache.

[2:15] That's why by the time this promise results, we know that all the cache has been restored. That's why I can move my render function in here so that my app only renders when my cache has fully been restored from local storage.

[2:29] By the way, even though this is a promise, it's still very quick to read from local storage, much much quicker than a network request anyway. Let's see this in action. If I look inside my application tab in the DevTools and I inspect the local storage for localhost, we can see that there's nothing in local storage currently.

[2:48] If I go to the network tab, watch the notes here. If I refresh the page, I'll get my loading spinner. We're also going to get two network requests. Whenever they resolve, that's when we can display the content. We saw this already.

[3:04] What's different now is that if I go back to my local storage now that we added our persist mechanism, we can see that there's a new key in here, Apollo-cache-persist, and the value it contains. Well, this is very familiar.

[3:18] We have here the first three notes that we display on the screen right here. We also have all of my queries for categories and notes. Cool. If I go back to the network tab, and if I refresh the page again now, we can see all the data loaded instantly and there were no network requests.

[3:38] What happened is that Apollo saw that we have some data in local storage, which then got restored to its in-memory cache. After that, the app was allowed to render.

[3:51] Because Apollo first checks the in-memory cache whenever a component makes a query, it can instantly provide the list of saved notes and all my categories. We made our app fast, instant almost, regardless of the network speed.

[4:05] Because now every time you refresh the app, we always use the same data that's coming from local storage, we run the risk of all this data getting old since we're never refreshing it from the server.

[4:18] Well, Apollo queries like my notes list allow you to specify a fetch policy option and the default is cache-first. This means that whenever my component makes the allNotes query, check the cache-first to see if there are any notes in there. If not, then go to the network and ask the server.

[4:36] This is good and it's probably the best default in most cases. What I'll change it to is the cache and network fetch policy. This tells it to immediately grab any cached data that is available. In the background, also go to the network to see if there's anything more recent on the backend.

[4:55] This is going to make my query both fast, because it loads instantly from the cache, but it also keeps my data fresh.

[5:02] Because I want this policy to be applied only the first time I upload so I can update my local storage with any new things coming from the network, I'm going to add the next fetch policy option and set it back to the default cache-first.

[5:17] This means that once it's done with the initial first query, any future queries revert back to using just the cache. Back in my browser, if I refresh the app, we immediately see my content. We also see a pending network request.

[5:31] This is the request we told our notes query to make just in case there are any updates from the backend. Cool. These options only apply to my notes query right now. We'd like them to really be applied to all of my queries. Let me remove them.

[5:47] If we go back to where we are creating the client, I'll specify some default options. I want my default watch query options to be fetch policy cache and network, and next fetch policy cache first.

[5:59] The reason we specified watch query is because that's the query type that the useQuery hook uses. Cool. If we go back to the browser and refresh the page, we're going to see the data instantly again, all my categories, but now we see two additional network requests.

[6:16] These are for my notes query, but we also get a new one for my categories query. In summary, we use the persist cache utility from the Apollo3-cache-persist package to automatically keep my in-memory Apollo cache in-sync with the browser's local storage.

[6:36] When the app first starts, persist cache will restore anything from my local storage back into my Apollo cache and then render the app.

[6:46] We then specified some default options for all useQuery hooks and told them that the first time they start up to load what they can from the cache, but also make a network request in the background and fetch any updates from the backend.

[7:02] For any future queries they make though, we told them to revert back to the default cache-first policy, so that we don't start spamming the backend with unnecessary requests.