1. 23
    Append GraphQL subscription updates to existing queries
    7m 25s

Append GraphQL subscription updates to existing queries

Rares Matei
InstructorRares Matei
Share this video with your friends

Social Share Links

Send Tweet

The useSubscription hook completely replaces previous data with any new data coming in. In this lesson, we'll use the "subscribeToMore" function provided on the useQuery hook to append notes pushed to us by the server at the top of our existing notes query. We'll also look at solving a conflict created by our "load more" functionality, which has a merge policy responsible for adding new items at the end of the existing notes query.

Instructor: [0:00] As we're watching this list now, we are now getting live note updates. The problem is, these new note updates, they override each other in this component. You'll notice that, after a few seconds, as a new note comes in, it overrides the previous new note.

[0:16] Ideally, we want to make this experience a bit more seamless for the user, and not even have a separate recent changes template. When a new note comes in, we just want to push it on top of the existing notes. As others come in, just continue pushing them to the top.

[0:31] Let's remove our new note template. Now our notes query gives us, in addition to a fetchMore function, it also gives us a subscribeToMore function. We called fetchMore when someone pressed the Load More button, but we want to subscribe to new items immediately as my component mounts.

[0:52] Let me use the useEffect hook and subscribe to more items in here. I'll give it the document or the query to subscribe to, which is just going to be my new shared note query that I defined here. I also need to move the variables I need to send it. I'll just remove my previous subscription since I don't need it anymore. Cool. Let's do some housekeeping.

[1:12] The subscribeToMore function returns us an unsubscribe callback, which I can return in my effect so that React disposes of any subscription or websocket connection whenever the component unmounts or whenever the useEffect hook has to rerun.

[1:29] Also, this subscription has an external dependency on my category. Whenever the category changes, I want React to unsubscribe from the current subscription and subscribe again using the new variables. For it to do that, I'll tell it that category is an external dependency that can change.

[1:49] This function will be called everyTimeCategoryChanges. everyTimeCategoryChanges, it will unsubscribe from the previous one and subscribe again to the new one. subscribeToMore is a bit more complex than fetchMore. With fetchMore, you only need to pass it the changed variables.

[2:06] With subscribeToMore, you're building up a new subscription query. Apollo can confer us safely how you want the new items to be merged in with the existing query. That's why you have to tell it explicitly by supplying an updateQuery function.

[2:21] This accepts a previous query result and a new subscription data. I can get my new shared note from subscriptiondata.data.newsharednote. Whenever the server pushes me a new note, this function will be called and I can retrieve the new note from it.

[2:38] Whatever I return from this function is going to be the new result for my notes query, which you can see has notes property which will contain my array of notes.

[2:49] I can return an object with a notes property. I want to add the new note at the beginning of it and all the other previous note after it. I also want to spread my previous query result up here. This is to ensure that things like pipe name remain as part of my cache query.

[3:07] Let's try this out. I have here my list of notes. If I wait a few seconds, oh, no! My new note appeared down here. Then all my other notes are duplicated again below it. Why did this happen? Oh, no! Even more notes are coming in and it just keeps duplicating my list over and over again.

[3:33] What is happening? Well, we are changing the notes query here. If you remember in my index.js file, whenever a write happens to the notes, our merge policy gets invoked, which takes the existing items and adds any new ones at the end of them.

[3:52] Every time we get pushed a new note, we're just sending it the whole set of existing notes plus the new one, which then gets added on top of the existing notes already in the cache. That's why we get this duplication over and over for each new note. This isn't going to work that well.

[4:10] There's a conflict here. We want to keep the Load More functionality. We can't really remove the merge policy. We also want this ability to add new notes to the existing list whenever the server pushes it to us.

[4:23] Let me invoke the useApollo Client hook, which will give our component access to the Apollo Client. From the Client, we can get access to the cache on which we can write items to a specific query.

[4:36] The query will be my allNotes query that I defined at the top and the date I want to replace it with is basically my return statement down here. I can just remove my default return function since I don't need it anymore.

[4:50] Remember how there are different versions of queries in my cache depending on what variables they were invoked with. In my case, I want to specifically write this new data to the notes query that was invoked with the current category ID variable.

[5:05] Finally, even with this strategy, we still have the same problem as before where if we try to write to my notes field, my merge interceptor will jump in and cause all those duplicates. I will add the override true option.

[5:18] Now whenever a write happens, the override true option will delete any existing items in the cache for this query, which will cause the existing notes in my merge interceptor to be undefined, to be empty.

[5:34] Then only my incoming notes will be part of the list. If I test this out in my browser and I wait a few seconds, I can see my new note come in at the top. If I scroll down, we just have my other three notes in here, so no duplicates.

[5:50] If we wait another few seconds, I'll see yet another new note just pushing down all existing notes in the list without any duplicates. If I click my Load More, our merge policy still works and it just adds three more notes at the end of my existing list.

[6:09] In summary, we use the subscribeToMore function, which useQuery gives us, to define how we want the new notes pushed to us by the server to be added to our existing notes query.

[6:20] We invoked it as part of a useEffect hook and defined the current category as a dependency. When my component is first mounted, Apollo will immediately subscribe to new items.

[6:33] If the category changes, it will unsubscribe from the existing connection and just make a new subscription with the new category variable. We then use the updateQuery function, which gives us the previous query result and the new data from my subscription.

[6:51] We got access to the Apollo Client via the useApollo Client hook, and then used the write query utility on the Apollo cache to write the new data to my allNotes query, making sure I target the correct version of my query in the cache for the current category.

[7:09] Finally, I use the override true option to make sure that the data I write in here completely replaces whatever exists currently in the cache. This allowed me to avoid conflicts with my existing merge policy that would have otherwise duplicated items for me.