March 21, 2017

Tutorial: GraphQL Mutations with React

Jonas Helfer
FrontendHow-to

In this tutorial, you’ll learn how to use a simple mutation to modify data on the server and keep the state synchronized on your clients. Specifically, we’ll create a mutation that adds an item to a list.

The mutation call you’ll write in this tutorial

This post is part of a tutorial series on GraphQL + React. Here are the other parts:

In this tutorial, we’ll do the following:

  1. Connect our frontend to the server
  2. Define the GraphQL mutation on the server
  3. Call the GraphQL mutation from a React component on the client
  4. Update the state on the client to make sure it’s in sync with the server

Each of these steps is very simple, so completing the whole tutorial should barely take 25 minutes.

If you haven’t done Parts 1 and 2 yet, you can either do those first or jump in right here and check out the starting state of this tutorial from the GitHub repo:

git clone <a href="https://github.com/apollographql/graphql-tutorial.git" target="_blank" rel="noreferrer noopener">https://github.com/apollographql/graphql-tutorial.git</a>
cd graphql-tutorial
git fetch
git checkout t3-start

I recommend that you check out the t3-start branch even if you’ve already done Parts 1 & 2, because we’ve made some changes to App.css and App.js to improve the layout and folder structure of the app.

To check if things worked, let’s start the GraphQL server:

cd server
npm install && npm start# ...
# GraphQL Server is now running on <a href="http://localhost:4000/graphiql" target="_blank" rel="noreferrer noopener">http://localhost:4000/graphiql</a>

Let’s also start the dev server that serves our front-end bundle in a separate console:

# assuming that you're in the graphql-tutorial directorycd client
npm install && npm start# ...
# The app is running at:
#
#    <a href="http://localhost:3000/" target="_blank" rel="noreferrer noopener">http://localhost:3000/</a>

If it worked, you’re ready to write your first mutation!

1. Connecting the frontend to the server

In the last tutorial, we built our server, but we didn’t connect it to our frontend yet. To do so, we’ll just need to make two minor changes on the server and the client.

Because the server is running on port 400o and the client is served from port 3000, we’ll need to enable CORS on the server. In this tutorial, we’ll use the cors package for Express/Connect:

# in the server directory (not the client!!)npm install --save cors

Now we just have to import cors in server/server.js and modify the following line to allow cross-origin requests from our front-end origin:

// ...
import cors from 'cors';// ... const server = express();
server.use('*', cors({ origin: 'http://localhost:3000' }));

On the frontend, we’ll need to import createNetworkInterface from react-apollo and then replace our mockNetworkInterface with one that connects to the server:

// client/src/App.jsimport {
  ApolloClient,
  ApolloProvider,
  createNetworkInterface, // <-- this line is new!
} from 'react-apollo';

You can remove all the code that was used to create the mockNetworkInterface (including the imports) and replace it with the following:

const networkInterface = createNetworkInterface({ 
  uri: '<a href="http://localhost:4000/graphql'" target="_blank" rel="noreferrer noopener">http://localhost:4000/graphql'</a>,
});const client = new ApolloClient({
  networkInterface,
});

Your client is now connected to the server, and you should see the following at localhost:3000:

“soccer” and “baseball” are loaded from the server

2. Defining the GraphQL mutation on the server

Now that the client is hooked up to the server, we can get started on the real task — writing a mutation to add a channel to our list of channels.

First, we’ll define the mutation in our schema by adding it in server/src/schema.js:

const typeDefs = `
type Channel {
  id: ID!                # "!" denotes a required field
  name: String
}

type Query {
  channels: [Channel]    # "[]" means this is a list of channels
}# The mutation root type, used to define all mutations.
type Mutation {
  # A mutation to add a new channel to the list of channels
  addChannel(name: String!): Channel
}
`;

The new Mutation type that we just added defines a single mutation — addChannel — which takes a single argument, the name of the new channel. The mutation returns a channel object, which we can then select fields on, just as with a query. Here’s an example of a valid mutation:

# an example mutation call:
mutation {
  addChannel(name: "basketball"){
    id
    name
  }
}

Of course this mutation won’t do anything until we define a resolver function for it. Our resolve functions live in server/src/resolvers.js, so let’s head over there and add a resolve function for our new addChannel mutation. The resolve function has to take the name provided as an argument, and generate an id for the new channel before adding it to the existing list.

Let’s change server/src/resolvers.js to add a new nextId variable and define the resolver for Mutation.addChanel:

const channels = /* ... */
let nextId = 3;export const resolvers = {
  Query: {
    channels: () => {
      return channels;
    },
  },
  Mutation: {
    addChannel: (root, args) => {
      const newChannel = { id: nextId++, name: args.name };
      channels.push(newChannel);
      return newChannel;
    },
  },
};

As you can see, the resolver just pushes a new channel to channels, increments the nextId and returns the newly created channel. If you head over to localhost:4000/graphiql, you should be able to run the example mutation from above and get a response.

The mutation returns the channel that was just added.

If you reload your client app on localhost:3000, you should now see the “basketball” channel show up.

Our newly inserted “basketball” channel shows up after reloading

Note: The channels are currently stored in memory only, so every time you restart the server, the newly added channels will disappear. We’ll hook things up to persistent storage in a future tutorial.

3. Calling the mutation from a React component

Now that we’ve verified that the mutation is working on the server, let’s write the necessary code to call it from the client. First, we’ll create an input component in src/components/AddChannel.js:

import React from 'react';const AddChannel = () => {
  const handleKeyUp = (evt) => {
    if (evt.keyCode === 13) {
      console.log(evt.target.value);
      evt.target.value = '';
    }
  };  return (
    <input
      type="text"
      placeholder="New channel"
      onKeyUp={handleKeyUp}
    />
  );
};export default AddChannel;

Our AddChannel component is very simple. So far it just consists of a text input element and a handleKeyUp function that prints the input text to the console and clears the input field when the user hits return (key code 13).

Let’s import it in src/components/ChannelsListWithData.js and place it right before our list of channels:

import AddChannel from './AddChannel';// ...const ChannelsList = ({ data: {loading, error, channels }}) => {
  if (loading) {
    return <p>Loading ...</p>;
  }
  if (error) {
    return <p>{error.message}</p>;
  }  return (
    <div className="channelsList">
      <AddChannel /> // <-- This is the new line.
      { channels.map( ch => 
        (<div key={ch.id} className="channel">{ch.name}</div>)
      )}
    </div>
  );
};

If it worked, you should now see the “New channel” input in your UI:

To make our input component call a GraphQL mutation, we have to wire it up with the GraphQL higher order component (HOC) from react-apollo (just like we did in the first tutorial). For mutations, the graphql HOC passes down a mutate prop, which we’ll call to execute the mutation.

import React from 'react';
import { gql, graphql } from 'react-apollo';const AddChannel = ({ mutate }) => {
  const handleKeyUp = (evt) => {
    if (evt.keyCode === 13) {
      evt.persist();
      mutate({ 
        variables: { name: evt.target.value }
      })
      .then( res => {
        evt.target.value = '';  
      });
    }
  };return (
    <input
      type="text"
      placeholder="New channel"
      onKeyUp={handleKeyUp}
    />
  );
};const addChannelMutation = gql`
  mutation addChannel($name: String!) {
    addChannel(name: $name) {
      id
      name
    }
  }
`;const AddChannelWithMutation = graphql(
  addChannelMutation
)(AddChannel);export default AddChannelWithMutation;

Let’s see if it worked and type something into the input box. Did the text disappear after you hit enter? If it did, the mutation was successful, and you should see the item after reloading the page. Of course, having to reload the page is not exactly a great user interaction, so in the next section we’ll find out why Apollo can’t know what the mutation means, and what we need to do to make the list re-render with the new item.

4. Updating the client state after a mutation

The reason our list of channels didn’t automatically re-render is that Apollo has no way of knowing that the mutation we just called has anything to do with the channels query that renders our list. Only the server knows that, but it has no way of notifying our client (for that we’d need a subscription, which we will cover in a future tutorial).

To update the client state after a mutation, we have three options:

  1. Refetch queries that could be affected by the mutation
  2. Manually update the client state based on the mutation result
  3. Use GraphQL subscriptions to notify us about updates

The refetch option is by far the simplest one, and it’s a great way to get an app working quickly, so we’ll do that for now.

To tell Apollo Client that we want to refetch the channels after our mutation completes, we pass it via the refetchQueries option on the call to mutate. Rather than writing the channels query again, we’ll export it from ChannelsListWithData.js and import it in AddChannel.js:

// AddChannel.js// ...
import { channelsListQuery } from './ChannelsListWithData';// ...    mutate({ 
      variables: { name: evt.target.value },
      refetchQueries: [ { query: channelsListQuery }], // <-- new
    })// ...

Now if we add a new channel, the list should refresh right away!

Our UI updates almost instantly after refetching the list of channels

That’s it; you’ve implemented our first GraphQL mutation!

Of course this will only update the UI after you make a mutation. If the mutation was initiated by another client, you won’t find out until you do a mutation of your own and refetch the list from the server. Most of the time that’s not an issue, but for real-time apps Apollo has a nice trick up its sleeve to transparently propagate updates to all clients with almost no effort for the programmer: Polling queries.

In order to turn it on, simply pass the pollInterval option with your channelsListQuery in src/components/ChannelsListWithData.js:

export default graphql(channelsListQuery, {
  options: { pollInterval: 5000 },
})(ChannelsList);

With this simple change, Apollo will rerun the query every 5 seconds, and your UI will be updated with the latest list of channels. You can test this by opening a new browser window and adding a channel there. The new channel should appear in the other window after a small delay.


Congratulations, you’ve reached the end of the third step in the GraphQL + React tutorial! You’ve added a mutation to your GraphQL schema, wrote a resolver for it, called the mutation from a React component and made sure the UI gets updated by refetching and polling. Together with Parts 1 & 2 of this tutorial series you’re now familiar with all the basics of writing a complete React + GraphQL app with Apollo.

To learn how to make your mutations more efficient and apparently faster, continue on to the next part, where you’ll learn about optimistic UI and store updates!

Part 4: GraphQL Mutations with optimistic UI and client side store updates

If you liked this tutorial and want to keep learning about Apollo and GraphQL, make sure to click the “Follow” button below, and follow us on Twitter at @apollographql and @helferjs.

Written by

Jonas Helfer

Stay in our orbit!

Become an Apollo insider and get first access to new features, best practices, and community events. Oh, and no junk mail. Ever.

Similar posts

September 23, 2020

Add GraphQL to Your Jetpack Compose Apps

by Martin Bonnin

Company