GraphQL concepts I wish someone explained to me a year ago

Part 5: Mutations (client implementation)

Naresh Bhatia
Naresh Bhatia
4 min readDec 25, 2018

--

Image by Rostyslav

In part 4 of this series we implemented mutations on our Bookstore server. In this part, we jump back to the client side and implement matching mutations there.

One part will be released every day this week. Follow me or subscribe to my newsletter to get updates on this series.

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 5-mutations-client

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. What’s new from last time? You should now be able to create and edit all the bookstore entities. Give it a try.

Side note
Due to a minor bug that creeped into this commit, the create use case will not work very well. It’s performing the correct mutation on the server. It’s also receiving the newly created entity back, which is saved in the Apollo Cache. However, the list appears to be unchanged because MobX observers prevent re-renderings when the props of a component have only shallowly changed.

This bug has been fixed in the master branch by sending the full data object returned by queries to AuthorsPanel, PublishersPanel and BooksPanel (instead of just the contained list). You can checkout the master branch to verify this.

Mutation 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 mutations in the client, we need to run the gql:types script to generate new TypeScript type definitions.

Add mutations to the AuthorsPanel

The AuthorsPanel is where all the author mutations are triggered:

  • When the Add button is clicked in the AuthorsPanel, the AuthorDialog is displayed. When the user clicks the save button on the dialog, the createAuthor mutation is triggered.
  • When a row in the author table is clicked, the AuthorDialog is displayed, pre-populated with the author that was clicked. When the user clicks the save button on the dialog, the updateAuthor mutation is triggered.

Let’s see how this is implemented. We first add the create and update mutations at the end of authors-panel.tsx:

const CREATE_AUTHOR = gql`
mutation CreateAuthor($name: String!) {
createAuthor(name: $name) {
id
name
}
}
`;

const UPDATE_AUTHOR = gql`
mutation UpdateAuthor($authorId: ID!, $name: String!) {
updateAuthor(authorId: $authorId, name: $name) {
id
name
}
}
`;

We then add two sections in the render() method to show the AuthorDialog in the create and update case. Below we show the code for the create case (the update case is similar):

{this.showAuthorDialog && this.isNewAuthor && (
<Mutation mutation={CREATE_AUTHOR}>
{createAuthor => (
<AuthorDialog
author={this.editedAuthor}
onSave={author => {
createAuthor({
variables: {
name: author.name
},
// Update AuthorsQuery in Apollo cache
// Needed only in this "Create" case
update: (
store,
{ data: { createAuthor } }
) => {
const data = store.readQuery({
query: GET_AUTHORS
}) as any;
data.authors.push(createAuthor);
store.writeQuery({
query: GET_AUTHORS,
data
});
}
});
this.hideAuthorDialog();
}}
onCancel={this.hideAuthorDialog}
/>
)}
</Mutation>
)}

Note that AuthorDialog is wrapped in the <Mutation> component provided by Apollo. It works in a similar way to the <Query> component that we discussed in part 3. Essentially, the <Mutation> component takes a GraphQL mutation as a prop and calls the render prop passing it the createAuthor function. When the user clicks the Save button in the AuthorDialog, the onSave method initiates a mutation by calling this createAuthor method. The createAuthor method also accepts an update function which is called on a successful update. This method is passed a reference to the store (Apollo cache) as well as the result sent by the server (the newly created author). We use these arguments to update the local Apollo cache using the readQuery and writeQuery methods.

Now that you understand how mutations work in the AuthorsPanel, it should be straightforward to understand how they work in the PublishersPanel and the BooksPanel.

Summary

We have now covered our basis with mutations on the client. This is what we’ve learned:

  • How to declare mutations on the client
  • How to trigger the mutation queries using Apollo’s <Mutation> component

We’re on the home stretch now–are you enjoying the series so far? I’d love to get your questions and comments.

In part 6, we will dive into subscriptions and add them to the Bookstore server.

Read Part 6: Subscriptions (server implementation) >

--

--