GraphQL concepts I wish someone explained to me a year ago
Part 7: Subscriptions (client implementation)
In part 6 of this series we implemented subscriptions on the Bookstore server. In this last and final part, we jump back to the client and implement matching subscriptions there.
- Part 1: GraphQL Basics
- Part 2: Queries (server implementation)
- Part 3: Queries (client implementation)
- Part 4: Mutations (server implementation)
- Part 5: Mutations (client implementation)
- Part 6: Subscriptions (server implementation)
- Part 7: Subscriptions (client implementation)
Source Code
You can download the code for this part by executing the following commands. Skip the first command if you have already cloned the repository.
git clone https://github.com/nareshbhatia/graphql-bookstore.git
cd graphql-bookstore
git checkout master
Test Drive
Before diving into the implementation, let’s look at how the final implementation works. Make sure you have yarn installed on your machine. Now execute the following commands:
cd apollo-bookstore-server
yarn
yarn dev
This starts the server. In a different shell, execute the following commands to start the client:
cd apollo-bookstore-client
yarn
yarn start
Point your browser to http://localhost:3000/. You should see the familiar GraphQL Bookstore client. To prove that subscriptions are working, open another instance of the client in a new browser window. Do some mutations in one window. You will see that both windows show the results of the mutation simultaneously. Really cool, isn’t it?
Subscription Implementation
Download schema and run codegen
Since the schema on the server has changed, we need to run the gql:schema
script again. Also, as we add subscriptions in the client, we need to run the gql:types
script to generate new TypeScript type definitions.
Add subscriptions to containers
We will add subscriptions to our container components. Recall, container components are responsible for fetching data from the server and handing it to the panels. Whenever the server sends us a subscription event, we will update the Apollo cache with the mutated entity. This will trigger a refresh of the component, displaying the changes.
Let’s use the authorMutated
event as an example. We declare the subscription at the end of authors-container.tsx
:
const AUTHOR_MUTATED = gql`
subscription AuthorMutated {
authorMutated {
mutation
node {
__typename
id
name
}
}
}
`;
We’ll now modify the AuthorsContainer
to subscribe to this event. This is a bit involved, so let’s take it one step at a time. Here’s the modified AuthorsContainer
:
export class AuthorsContainer extends React.Component {
unsubscribe = null;
render() {
return (
<DefaultQuery query={GET_AUTHORS}>
{({ data, subscribeToMore }) => {
// Subscribe to author mutations - only once
if (!this.unsubscribe) {
this.unsubscribe =
subscribeToAuthorMutations(
subscribeToMore
);
}
return <AuthorsPanel data={data} />;
}}
</DefaultQuery>
);
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
}
Note that we are subscribing to the authorMutated
event in the render
method by calling subscribeToAuthorMutations()
. However, we should subscribe to this event only once during the lifetime of the component. Hence we retain the unsubscribe
function returned by the subscribe method in a class property called unsubscribe
. We also check for the presence of this function — if it is already present, we know that we’re already subscribed and we avoid subscribing again.
Now let’s look at the subscribeToAuthorMutations()
function. This function takes subscribeToMore
as a parameter. Note that subscribeToMore
has been passed to us as an argument of the render prop method above — it is a function that sets up a subscription and returns a function that you can use to unsubscribe. You can read more about subscribeToMore
here. We are simply passing it down.
function subscribeToAuthorMutations(subscribeToMore) {
return subscribeToMore({
document: AUTHOR_MUTATED,
updateQuery: (prev, { subscriptionData }) => {
const data: AuthorMutated = subscriptionData.data;
if (!data) return prev;
const authorMutated = data.authorMutated;
switch (authorMutated.mutation) {
case MutationType.CREATED: {
const newAuthor = authorMutated.node;
// Don't double add the author
if (findAuthor(prev.authors, newAuthor.id)) {
return prev;
} else {
// Author not found, add it
return Object.assign({}, prev, {
authors: [...prev.authors, newAuthor]
});
}
}
case MutationType.UPDATED: {
const updatedAuthor = authorMutated.node;
// Replace previous author with updated one
return Object.assign({}, prev, {
authors: prev.authors.map(author =>
author.id === updatedAuthor.id
? updatedAuthor
: author
)
});
}
case MutationType.DELETED: {
const deletedAuthor = authorMutated.node;
// Delete author
return Object.assign({}, prev, {
authors: prev.authors.filter(
author => author.id !== deletedAuthor.id
)
});
}
default:
return prev;
}
}
});
}
The subscribeToAuthorMutations()
function does the actual subscription to the authorMutated
event by calling subscribeToMore
. The updateQuery
callback is called whenever a new authorMutated
event is received. The callback then updates the Apollo cache based on the mutation type. This in turn refreshes the view and shows the result of the mutation.
Summary
We have now covered everything to do with subscriptions on the client. This is what we learned:
- How to declare subscriptions on the client
- How to subscribe to subscription events
- How to update the Apollo cache when a subscribed event is received
With this, you should feel empowered to write your own GraphQL applications from scratch. If you have any questions or comments, I’d love to hear from you.
If you found this series helpful, please share it with your fellow developers. Until then, happy computing!