GraphQL concepts I wish someone explained to me a year ago

Part 7: Subscriptions (client implementation)

Naresh Bhatia
Naresh Bhatia
4 min readDec 27, 2018

--

Image based on Rostyslav’s pen

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.

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!

--

--