Lists of notes can get very big. We don't want to load all existing notes on the database every time the user loads the app. We'd like to re-create the Twitter timeline experience, and only when the user scrolls to the bottom, then load a few more notes. For this, we'll start off by looking at the fetchMore utility, that is provided by the useQuery hook, to trigger loading of more notes from the server. Then, we'll look at defining a merge field policy, to define how these new loaded notes should be combined with the existing notes. Finally, we'll look at specifying keyArgs for queries, and how it helps us define by which variables to key certain queries in the cache.
Instructor: [0:00] our list of notes can potentially grow infinitely large as people add more and more notes to this list. If we keep loading all of them every time the app opens, eventually, it's going to start freezing. What we'd like to do, is load only the three most recent notes when the app opens, and then request more as the user is using the app, similar to Twitter or Facebook's timeline.
[0:25] Our notes query accepts two more variables -- an offset, which we can use to tell it at which index to start giving us notes from, and a limit, where we can specify how many notes we want back. I'll pass them to the query itself, and then I'll initialize this query with offset, which means, give us the notes from the beginning, but only the first three of them.
[0:49] Offset and limit are one way you can implement this load more functionality. They need to be implemented and supported on your backend. Cool.
[0:56] If I save this and refresh the browser, we can see that now we only get these three notes here. Cool. Let's go to our component template down here and just below our list of notes, I'm going to add this Load More button, which has an onClick handler.
[1:13] If you look in the browser, we can see this button down here. We'd like whenever it's clicked to load the next three notes, and then the next three, and so on.
[1:22] The useQuery hook also gives us a fetchMore function, which we can use to build functionality that requires adding more items later on on top of an initial query. I will call it an our load more click handler.
[1:37] Every time we call it, we want to request new notes, starting from an offset, that is the length of our currently loaded list of notes. Let's say we have a list of only three notes. When we click load more, we want to load another three notes, starting from index three.
[1:56] That would be notes three, four, and five, which is going to give us this new list. If we press load more yet again, we want to load another three notes, but from offset six this time. That's notes six, seven, and eight, which will result in an even bigger list.
[2:11] It turns out that our offsets will always be the length of the current list of notes we're looking at. It's going to be length three in this case and length six in this case. That is why we use it up here. Notice how we only pass offset. We don't pass limit or category.
[2:30] Fetch more will just reuse whatever you don't give it from the initial query, which is exactly what we want. We only want to change the offset when the Load More button is clicked. Let's see what this does in the browser.
[2:41] If we look at the Apollo cache, we can see that it keeps track of all the variables our notes query was invoked with. The first category, a limit of three and an offset of zero. This is a good default behavior because now if we change the category, it can create a brand new list for the second category.
[3:01] Now I can just switch between these two cached queries easily because it kept track individually of what notes to load for each one of the different categories. Now let me switch to the network tab and click on Load More. We can see that nothing changed in our list, which is weird.
[3:17] A new request got created, invoked with our new offset of three and returning us some new notes with IDs four, five, and six. They are getting returned. If we look at the cache, we can see that it created yet again a new entry in our cache for the notes.
[3:37] This time with category one, but the offset of three. Our list component is still listening to this first query because remember, we initialized it with offset zero. That's why nothing in the component itself has changed because it doesn't know about this new list of notes.
[3:52] We saw how the Apollo defaults can be good for most cases. It allowed us to build the category function, and it worked perfectly from the beginning. Let's see if we can tweak them a bit. My cache accepts a config with a type policies option.
[4:06] Here I can customize the behavior of different fields in my cache. Under the root query type from its fields, I want to edit default behaviors for the notes query. The first thing I'll customize is the key args. This tells Apollo what variables to use to store different lists of notes in the cache.
[4:24] The default is store a new one whenever any of the variables changed, so all of them. This is the default, but let me do the opposite of that and tell it not to key it by anything. I'll set it to false.
[4:38] Back in my browser, we can immediately see that we don't have any extra variable information for the notes query now because we disabled all the variables, which isn't very good because if I switch categories now, the list is not going to change.
[4:54] The moment I click on a new category, Apollo goes, "Are there already some notes in the cache? Any notes at all? I don't care about variables anymore." It sees this pre-existing list of notes that we already had loaded in, so it just uses those.
[5:11] It doesn't request any new ones from the backend. We don't want to disable all variables then. We want to keep creating new entries for each category ID. Now it's not going to create separate queries when just offset or limit changes because they're not part of this list.
[5:27] Next, I'll define a merge function which will be called with a list of existing notes and a list of new incoming notes. This merge function allows us to define how new items get written to this field. By default, any existing notes get replaced with any incoming notes.
[5:46] In our case, we want to add any incoming notes to the end of any existing notes. The first time the merge function gets called for each category, existing notes will be undefined because there's nothing in the cache. I'll just initialize them with an empty list.
[6:04] If we go back to the browser, we can already see that the list of notes are being cached by categories again. If we click load more, we get some new notes at the end of the list.
[6:16] If we look in the cache, we can see that because we told it to ignore offset and limit, we still have a single notes query with six items now. Of course, only when we switch categories, that's when Apollo creates a new query for the new category.
[6:31] That's something to keep in mind about this because we only key by the category. Whenever we switch to a new category that hasn't been loaded in the cache before, the existing notes will be empty because the new entry in the cache is being created.
[6:44] That's why our list got replaced when we switched the categories. If we just change the offset, the existing notes will contain something from the previous offset. That's why you could see the new incoming notes being added on top of the existing notes.
[6:59] In summary, we use the fetchMore utility from the useQuery hook to load some new notes whenever the Load More button is clicked, starting from an offset that is the length of our current list of notes.
[7:11] We then defined a type policy for the notes field under the root queries type and said that we only want to key it by category ID. If offset or limit changes, just consider it part of the existing same query.
[7:28] Then when we click Load More and those new notes come in and Apollo wants to write them to the cache, instead of replacing any existing list, we define the custom right interceptor that takes in some new incoming notes, and just appends them to existing ones before committing those changes to the cache.