Tutorial: GraphQL Mutations with React
Jonas Helfer
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.
This post is part of a tutorial series on GraphQL + React. Here are the other parts:
- Part 1 — The Front-end: declarative data fetching with GraphQL
- Part 2 — The Server: setting up a simple GraphQL server in 5 steps
- Part 3 —Mutations (you’re reading it right now)
- Part 4 — Mutations with optimistic UI and client side store updates
- Part 5 — Input Types and Custom Resolvers
- Part 6 — Subscriptions on the server
- Part 7 — GraphQL Subscriptions on the Client
- Part 8 — Pagination
In this tutorial, we’ll do the following:
- Connect our frontend to the server
- Define the GraphQL mutation on the server
- Call the GraphQL mutation from a React component on the client
- 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 https://github.com/apollographql/graphql-tutorial.git 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 http://localhost:4000/graphiql
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: # # http://localhost:3000/
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: 'http://localhost:4000/graphql', });const client = new ApolloClient({ networkInterface, });
Your client is now connected to the server, and you should see the following at localhost:3000:
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.
If you reload your client app on localhost:3000, you should now see the “basketball” channel show up.
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:
- Refetch queries that could be affected by the mutation
- Manually update the client state based on the mutation result
- 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!
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.