How to Customize Apollo Client Caching and Fetch Policy
We discuss Apollo Client's intelligent caching system and ways one can manipulate the fetch policy of query requests.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
Lesson Transcript
[00:00 - 00:20] One thing we've mentioned before, once or twice during the course, was how React Apollo doesn't only provide declarative means for data fetching, but another incredibly useful advantage for it is its intelligence caching system that is automatically set up with no configuration on our part. And we've seen some examples of this.
[00:21 - 00:31] Let's see another example. Assume we're in the homepage, and if we refresh the homepage, the listings or the premium listings that are to be shown are essentially fetched from the server, right?
[00:32 - 00:49] We make a network request to get this data, which is why we see the loading state before we see the final UI state. However, once this data is available and shown to us, if I was to go to another page in my app, let's say the host page, and if I was to come back to my homepage, that information isn't fetched again.
[00:50 - 01:13] The reason being, once fetched, Apollo caches that data, and once you go back to the page, it recognizes that data already exists, and it serves that information from the cache, which helps avoid unnecessary network requests when the data is already available. Okay, so with that said, let's see how this can behave in certain specific use cases.
[01:14 - 01:30] The very first use case we'll try is, what if we attempt to create a listing that is the highest priced listing in our application? So if we recall, we show the four highest priced listings in the homepage, and we consider these to be the premium listings.
[01:31 - 01:42] If I was to create a listing right now that had a higher price than any of these, let's say $500 a day, and if I was to navigate back to the homepage, what would be the behavior that we would want to see? So let's try it out.
[01:43 - 01:55] I'll go to the host components or the host page in our app, and I'll fill out this information. So let's say house, maximum number of guests is too large, better mansion, modern, clean, etc.
[01:56 - 02:21] 251 North Bristol Avenue, LA, California, 90210 will provide a valid listing image. And finally for the price, here we'll say something like $400 per day, which is graded in the highest priced listing, which was around $240.
[02:22 - 02:37] If I were to click submit, we'll see the loading state, and we've successfully created the listing. However, if I just simply navigate straight to the homepage or the index route, scroll down, that particular listing isn't being shown.
[02:38 - 02:48] If I was to now refresh the page, scroll back down, I get to see the highest price listing. Why do we see that behavior?
[02:49 - 02:57] Because once again, that information is cached. Apollo thinks that, hey, we already have this data, we're not going to make another network request to get new data.
[02:58 - 03:11] So this is where we can perhaps tell our Apollo client that maybe if we're trying to navigate to the homepage, we should always try to get information from the network. And if only the network data isn't available, then get that information from the cache.
[03:12 - 03:29] So essentially, we'll want to update the fetch policy of the query we make in the homepage. And here are some of the documentation that highlights the fetch policy option that essentially allows us to declare how our components will interact with the Apollo data cache.
[03:30 - 03:43] By default, the option specified is cache first, which essentially states that we're always going to try reading data from the cache. And if the data is available in the cache to fulfill the query, then the network request won't be made.
[03:44 - 03:52] There's also other options. There's the cache and network option, which would say the policy would attempt Apollo to try to read the data from the cache.
[03:53 - 04:05] However, whether or not the data is in the cache, this policy would always execute the query and then update the information if new data is available. And then there's the more restrictive options to be network only.
[04:06 - 04:12] In other words, don't ever get data from the cache. There's cache only, never execute a query using the network interface.
[04:13 - 04:25] There's the no cache option as well that says never return data from the cache. And then this one differs from the network only option since it won't actually write any data to the cache as well after the query completes.
[04:26 - 04:38] Now the ones at the below here from network only down to no cache are a little bit more restrictive. And what we tend to find personally is we usually stick with the default setting of having information just be available in the cache.
[04:39 - 04:51] And only when we notice that, hey, this might be a good place for it to actually make a network request, do we opt in to using the cache and network option. So let's actually see an example of this.
[04:52 - 05:05] We'll go back to our code and we'll look for the component that essentially has those premium listings in the homepage. And we make that query for that component in that component for that information in the home section components.
[05:06 - 05:15] And here is essentially the query we've established to query for listings that are of the highest price. This is where we apply the filter and where we specify the limited page.
[05:16 - 05:25] This is where we can declare another option specified as fetch policy. And then we can specify the fetch information that we want Apollo to take with this query.
[05:26 - 05:42] And in this case, we can say perhaps cache and network. Notice that this particular policy expects a set number of different values, either cache, cache first, network only, cache only, et cetera.
[05:43 - 05:52] So if I was to type something that didn't make any sense, TypeScript will actually warn us. But in this case, we want to cache and network.
[05:53 - 06:06] And now if I was to go back to the homepage, even before we create the actual listing, if I was to go to any other page at this moment, let's say the host page. And if I was to go back to the homepage, we'll see the network request being made again.
[06:07 - 06:12] And again, why? Because in this context, we're saying cache and network get that information from the cache.
[06:13 - 06:40] However, also make that network request right away as well to get any new information. Now if I was to go and try to create another listing, let's say we'll type some of the information as we had before.
[06:41 - 06:49] I'll use a different image here just to differentiate it from the previous listing we've just created. And if I was to add a price even higher than what we had before, let's say $500 per day.
[06:50 - 07:04] And if I was to click submit, the mutation with fire, we're taking to the listing page, which tells us we've successfully created the listing. And now if I go to the index page or the homepage, I see the newly created listing.
[07:05 - 07:20] And once again, the cache and network option is incredibly helpful because if for some reason or another, I go to my network tab and I try to say I'm in the offline states. And I try to navigate elsewhere and come back here, that information still arrives from the cache, right?
[07:21 - 07:26] The cache and network option doesn't say avoid the cache completely. It simply says get information from the cache.
[07:27 - 07:32] However, I think I made a mistake that was tried here. So I'll leave this network, I'll leave the network tab open.
[07:33 - 07:44] If I go to the host page, come back to the homepage, as you can see that information is still available. Even though the network request fails, it still says, hey, this data is available in the cache, let's just show it by default.
[07:45 - 07:57] And if only the network request is successful, does it actually update that information? Another similar example of where we can do something similar would be if we were to create a listing and then visit the user page.
[07:58 - 08:08] If I was to go to the user page right now for the first time, I'll see a network request being made and I'll see the listings that I own. I've created one before and these are the two newer ones that I've just created .
[08:09 - 08:20] However, in this case, if I go to the host page, fill in the form, create a new listing and then navigate straight to the user page, that network request won't be made again. So I won't be able to see the newly created listing.
[08:21 - 08:49] So similarly, if I was to visit the user component where we actually make the query for the user and if I was to find a particular query, I can add a new option labeled fetch policy and in this fetch policy option, I can specify cache and network. And now if I was to visit the client application, I'm already in the user page.
[08:50 - 09:07] So this information has been requested. I go to the host page right now, create another listing.
[09:08 - 09:16] I'll give this title something that we recognize we've just created. I'll just say large bell or mansion user to just say this is for the user page we want to see.
[09:17 - 09:28] I'll provide one of the images we had before, provide a valid price. In this case, price isn't what we're looking for here.
[09:29 - 09:33] So it doesn't really matter what we provide. Let's just say 230 and successful.
[09:34 - 09:55] I'm taking to the listing page, but now if I go to the user page, I'll see a network request being made automatically and I see the newly created listing being shown. So the user page as well as the home page is where the two pages where we can say we want the queries to have the cache and network fetch policy.
[09:56 - 10:09] Now do keep in mind there's sort of a give and take with this approach. For example, now in the user page, by having the cache and network property, if I was to go to any other page and any moment in time I try to visit the user page, I'll see the loading states, right?
[10:10 - 10:21] Because the loading states we've set up or the page skeleton we've set up was for the entire user query. And the user query controls the user profile information, the listings information, as well as the bookings information.
[10:22 - 10:43] So though we have the added benefits for the listing section to be updated all the time, we have sort of the small disadvantage because every single time we visit the user page, we have to see the loading state before we see information that might have already existed before. The home page in our app is a little bit better because in the home page, the majority of the page isn't dependent on this premium listings, right?
[10:44 - 11:08] So as just from a UI perspective, if I was to go to the whole section, come back to the home page, I still see most of the home page, but just the one section where we have premium listings is where we're actually loading or requesting information. So this is the one thing you have to keep in mind if you sort of set up or you style or you establish your loading states and how you want your caching and network capability to come into mind.
[11:09 - 11:21] Now, are there other options we can consider instead of having a network request be made, let's say for the user page, every single time we try to visit this page? Yes, there's other things that can be done.
[11:22 - 11:41] One other thing that can be done is the capability that React Apollo provides to directly update the cache. So keep in mind the cache is just a data store that contains information about the queries and mutations that have been conducted and sort of represent the actual states of the application, right?
[11:42 - 11:51] Instead of just making a network request to then update the cache, we can directly update the cache instead. So we're in the Apollo client documentation here and I'm not going to spend a lot of time.
[11:52 - 12:00] I'll try to link this in the documentation in the lesson. However, we're going to take an example of an update method that's shown here that shows how an update is made to the cache.
[12:01 - 12:14] So in this context, there's a mutation being fired with the use mutation hook and add to do mutation is being fired. There's the request function as being used in the template and then in one of the options here, there's an update method.
[12:15 - 12:27] And this update method has the cache data available or the cache object available as a parameter as well as the data from the actual mutation results. And in this context, what it says is it reads the cache.
[12:28 - 12:40] So there exists a read query method that essentially tries to run the query, a specific query on the cache. And it basically says, does the cache have data that can fulfill this query?
[12:41 - 12:56] In other words, does the cache have data from this query? If so, the cache will return this information and then a write query method is done to essentially write to the cache for where the query has been made to directly update the cache with new data.
[12:57 - 13:01] Right? So as you can see here, this is a little bit more, I guess, steps involved.
[13:02 - 13:13] However, the use case here is to avoid making a network request and trying to directly update the cache to be in the most present moment. However, there's a few things that need to be kept in mind here.
[13:14 - 13:31] One thing in particular is if the cache can't read information for a query, in other words, if the cache doesn't have that data, it would actually throw an error. So there has to be some good error prevention setups being made to say if only the cache has this information, then directly write this information, otherwise, don't.
[13:32 - 13:40] So that's one approach that can be made. Another option or another approach is using another property known as refetch queries.
[13:41 - 14:00] Refetch queries, according to the pull of client documentation, and we 100% agree, is the simplest way of updating the cache. So instead of directly trying to update a particular cache value, what refetch queries essentially does is it essentially specifies the one or more queries that you want to run after a mutation is complete.
[14:01 - 14:30] So in our use case, assume we say when a host listing mutation is complete, we can say right after it's been successful, refetch the listings query for the home page and the user query in the user page. So the query information is directly being fetched again, the cache is being updated, and then if we were to visit the user page at that moment, the cache should have the latest information, and we won't have to make another network request to get that information.
[14:31 - 14:51] So it's sort of just a first step for making the network request just for the queries in mind before actually making the entire request for the query when you visit the page. So just as a personal opinion, personally, we prefer to not avoid using the capability directly update the cache this way.
[14:52 - 14:58] There's oftentimes certain steps have to be kept in mind. It isn't very apparent from trying to understand what to do.
[14:59 - 15:01] It's doable. It's not bad, but we noticed that it's not fully necessary.
[15:02 - 15:20] And depending on the UI of our app, oftentimes we're usually okay with just using the cache and network approach for the queries that we want to network request being made . And once again, the great capability for using the cache and network fetch policy is if the network actually fails, at least we get information from the cache.
[15:21 - 15:30] So the user might see some outdated information, but the page doesn't necessarily fail completely. Another thing we do want to mention is we're building a single page application .
[15:31 - 15:43] So in our context, we don't expect the server to ever return anything different . And we're going to get the same index from the server, which we can always rely on a Apollo's cache to sort of cache information.
[15:44 - 15:54] If you were building a server-side rendered application, there's oftentimes a few other things that has to be kept in mind. The Apollo documentation does give a few other options for server-side rendered applications.
[15:55 - 15:59] But in our context, we're building a single page app. So we're not concerned with what the server is going to return.
[16:00 - 16:11] As long as the server returns the initial web page, regardless of what route you visit, you know, JavaScript in this context takes control. So it's a little bit simpler in that use case.
[16:12 - 16:15] Hopefully that was informative. I just wanted this lesson to be a little bit more of a discussion point.
[16:16 - 16:29] It wasn't necessarily a lesson to focus on writing a lot of code and just sort of discuss some of our opinions and our thoughts of Apollo's cache, how useful it is, and sometimes a few other things that can be done to just make certain use cases better in certain examples.
This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
![Thumbnail for the \newline course TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two](/_next/image?url=https%3A%2F%2Fs3.amazonaws.com%2Fassets.fullstack.io%2Fn%2F20200122213931033_tinyhouse-video-banner.png&w=3840&q=75)
One thing we've mentioned before, once or twice during the course, was how React Apollo doesn't only provide declarative means for data fetching, but another incredibly useful advantage for it is its intelligence caching system that is automatically set up with no configuration on our part. And we've seen some examples of this. Let's see another example. Assume we're in the homepage, and if we refresh the homepage, the listings or the premium listings that are to be shown are essentially fetched from the server, right? We make a network request to get this data, which is why we see the loading state before we see the final UI state. However, once this data is available and shown to us, if I was to go to another page in my app, let's say the host page, and if I was to come back to my homepage, that information isn't fetched again. The reason being, once fetched, Apollo caches that data, and once you go back to the page, it recognizes that data already exists, and it serves that information from the cache, which helps avoid unnecessary network requests when the data is already available. Okay, so with that said, let's see how this can behave in certain specific use cases. The very first use case we'll try is, what if we attempt to create a listing that is the highest priced listing in our application? So if we recall, we show the four highest priced listings in the homepage, and we consider these to be the premium listings. If I was to create a listing right now that had a higher price than any of these, let's say $500 a day, and if I was to navigate back to the homepage, what would be the behavior that we would want to see? So let's try it out. I'll go to the host components or the host page in our app, and I'll fill out this information. So let's say house, maximum number of guests is too large, better mansion, modern, clean, etc. 251 North Bristol Avenue, LA, California, 90210 will provide a valid listing image. And finally for the price, here we'll say something like $400 per day, which is graded in the highest priced listing, which was around $240. If I were to click submit, we'll see the loading state, and we've successfully created the listing. However, if I just simply navigate straight to the homepage or the index route, scroll down, that particular listing isn't being shown. If I was to now refresh the page, scroll back down, I get to see the highest price listing. Why do we see that behavior? Because once again, that information is cached. Apollo thinks that, hey, we already have this data, we're not going to make another network request to get new data. So this is where we can perhaps tell our Apollo client that maybe if we're trying to navigate to the homepage, we should always try to get information from the network. And if only the network data isn't available, then get that information from the cache. So essentially, we'll want to update the fetch policy of the query we make in the homepage. And here are some of the documentation that highlights the fetch policy option that essentially allows us to declare how our components will interact with the Apollo data cache. By default, the option specified is cache first, which essentially states that we're always going to try reading data from the cache. And if the data is available in the cache to fulfill the query, then the network request won't be made. There's also other options. There's the cache and network option, which would say the policy would attempt Apollo to try to read the data from the cache. However, whether or not the data is in the cache, this policy would always execute the query and then update the information if new data is available. And then there's the more restrictive options to be network only. In other words, don't ever get data from the cache. There's cache only, never execute a query using the network interface. There's the no cache option as well that says never return data from the cache. And then this one differs from the network only option since it won't actually write any data to the cache as well after the query completes. Now the ones at the below here from network only down to no cache are a little bit more restrictive. And what we tend to find personally is we usually stick with the default setting of having information just be available in the cache. And only when we notice that, hey, this might be a good place for it to actually make a network request, do we opt in to using the cache and network option. So let's actually see an example of this. We'll go back to our code and we'll look for the component that essentially has those premium listings in the homepage. And we make that query for that component in that component for that information in the home section components. And here is essentially the query we've established to query for listings that are of the highest price. This is where we apply the filter and where we specify the limited page. This is where we can declare another option specified as fetch policy. And then we can specify the fetch information that we want Apollo to take with this query. And in this case, we can say perhaps cache and network. Notice that this particular policy expects a set number of different values, either cache, cache first, network only, cache only, et cetera. So if I was to type something that didn't make any sense, TypeScript will actually warn us. But in this case, we want to cache and network. And now if I was to go back to the homepage, even before we create the actual listing, if I was to go to any other page at this moment, let's say the host page. And if I was to go back to the homepage, we'll see the network request being made again. And again, why? Because in this context, we're saying cache and network get that information from the cache. However, also make that network request right away as well to get any new information. Now if I was to go and try to create another listing, let's say we'll type some of the information as we had before. I'll use a different image here just to differentiate it from the previous listing we've just created. And if I was to add a price even higher than what we had before, let's say $500 per day. And if I was to click submit, the mutation with fire, we're taking to the listing page, which tells us we've successfully created the listing. And now if I go to the index page or the homepage, I see the newly created listing. And once again, the cache and network option is incredibly helpful because if for some reason or another, I go to my network tab and I try to say I'm in the offline states. And I try to navigate elsewhere and come back here, that information still arrives from the cache, right? The cache and network option doesn't say avoid the cache completely. It simply says get information from the cache. However, I think I made a mistake that was tried here. So I'll leave this network, I'll leave the network tab open. If I go to the host page, come back to the homepage, as you can see that information is still available. Even though the network request fails, it still says, hey, this data is available in the cache, let's just show it by default. And if only the network request is successful, does it actually update that information? Another similar example of where we can do something similar would be if we were to create a listing and then visit the user page. If I was to go to the user page right now for the first time, I'll see a network request being made and I'll see the listings that I own. I've created one before and these are the two newer ones that I've just created . However, in this case, if I go to the host page, fill in the form, create a new listing and then navigate straight to the user page, that network request won't be made again. So I won't be able to see the newly created listing. So similarly, if I was to visit the user component where we actually make the query for the user and if I was to find a particular query, I can add a new option labeled fetch policy and in this fetch policy option, I can specify cache and network. And now if I was to visit the client application, I'm already in the user page. So this information has been requested. I go to the host page right now, create another listing. I'll give this title something that we recognize we've just created. I'll just say large bell or mansion user to just say this is for the user page we want to see. I'll provide one of the images we had before, provide a valid price. In this case, price isn't what we're looking for here. So it doesn't really matter what we provide. Let's just say 230 and successful. I'm taking to the listing page, but now if I go to the user page, I'll see a network request being made automatically and I see the newly created listing being shown. So the user page as well as the home page is where the two pages where we can say we want the queries to have the cache and network fetch policy. Now do keep in mind there's sort of a give and take with this approach. For example, now in the user page, by having the cache and network property, if I was to go to any other page and any moment in time I try to visit the user page, I'll see the loading states, right? Because the loading states we've set up or the page skeleton we've set up was for the entire user query. And the user query controls the user profile information, the listings information, as well as the bookings information. So though we have the added benefits for the listing section to be updated all the time, we have sort of the small disadvantage because every single time we visit the user page, we have to see the loading state before we see information that might have already existed before. The home page in our app is a little bit better because in the home page, the majority of the page isn't dependent on this premium listings, right? So as just from a UI perspective, if I was to go to the whole section, come back to the home page, I still see most of the home page, but just the one section where we have premium listings is where we're actually loading or requesting information. So this is the one thing you have to keep in mind if you sort of set up or you style or you establish your loading states and how you want your caching and network capability to come into mind. Now, are there other options we can consider instead of having a network request be made, let's say for the user page, every single time we try to visit this page? Yes, there's other things that can be done. One other thing that can be done is the capability that React Apollo provides to directly update the cache. So keep in mind the cache is just a data store that contains information about the queries and mutations that have been conducted and sort of represent the actual states of the application, right? Instead of just making a network request to then update the cache, we can directly update the cache instead. So we're in the Apollo client documentation here and I'm not going to spend a lot of time. I'll try to link this in the documentation in the lesson. However, we're going to take an example of an update method that's shown here that shows how an update is made to the cache. So in this context, there's a mutation being fired with the use mutation hook and add to do mutation is being fired. There's the request function as being used in the template and then in one of the options here, there's an update method. And this update method has the cache data available or the cache object available as a parameter as well as the data from the actual mutation results. And in this context, what it says is it reads the cache. So there exists a read query method that essentially tries to run the query, a specific query on the cache. And it basically says, does the cache have data that can fulfill this query? In other words, does the cache have data from this query? If so, the cache will return this information and then a write query method is done to essentially write to the cache for where the query has been made to directly update the cache with new data. Right? So as you can see here, this is a little bit more, I guess, steps involved. However, the use case here is to avoid making a network request and trying to directly update the cache to be in the most present moment. However, there's a few things that need to be kept in mind here. One thing in particular is if the cache can't read information for a query, in other words, if the cache doesn't have that data, it would actually throw an error. So there has to be some good error prevention setups being made to say if only the cache has this information, then directly write this information, otherwise, don't. So that's one approach that can be made. Another option or another approach is using another property known as refetch queries. Refetch queries, according to the pull of client documentation, and we 100% agree, is the simplest way of updating the cache. So instead of directly trying to update a particular cache value, what refetch queries essentially does is it essentially specifies the one or more queries that you want to run after a mutation is complete. So in our use case, assume we say when a host listing mutation is complete, we can say right after it's been successful, refetch the listings query for the home page and the user query in the user page. So the query information is directly being fetched again, the cache is being updated, and then if we were to visit the user page at that moment, the cache should have the latest information, and we won't have to make another network request to get that information. So it's sort of just a first step for making the network request just for the queries in mind before actually making the entire request for the query when you visit the page. So just as a personal opinion, personally, we prefer to not avoid using the capability directly update the cache this way. There's oftentimes certain steps have to be kept in mind. It isn't very apparent from trying to understand what to do. It's doable. It's not bad, but we noticed that it's not fully necessary. And depending on the UI of our app, oftentimes we're usually okay with just using the cache and network approach for the queries that we want to network request being made . And once again, the great capability for using the cache and network fetch policy is if the network actually fails, at least we get information from the cache. So the user might see some outdated information, but the page doesn't necessarily fail completely. Another thing we do want to mention is we're building a single page application . So in our context, we don't expect the server to ever return anything different . And we're going to get the same index from the server, which we can always rely on a Apollo's cache to sort of cache information. If you were building a server-side rendered application, there's oftentimes a few other things that has to be kept in mind. The Apollo documentation does give a few other options for server-side rendered applications. But in our context, we're building a single page app. So we're not concerned with what the server is going to return. As long as the server returns the initial web page, regardless of what route you visit, you know, JavaScript in this context takes control. So it's a little bit simpler in that use case. Hopefully that was informative. I just wanted this lesson to be a little bit more of a discussion point. It wasn't necessarily a lesson to focus on writing a lot of code and just sort of discuss some of our opinions and our thoughts of Apollo's cache, how useful it is, and sometimes a few other things that can be done to just make certain use cases better in certain examples. [BLANK_AUDIO]