Apollo Mutation with Create React App

Before proceed I hope you have some knowledge on Graphql, React and ES6. Here I am going to discuss only client side implementation of Apollo Mutation and updating the cache after completing the mutation. For this I use create-react-app boilerplate and adding Apollo Mutation to that.

Steps in brief.

  1. create new react app using create-react-app
  2. install required npm packages
  3. Adding Apollo Mutation
  4. Adding Apollo Query
  5. Update Apollo Query cache after mutation

 

Step 1: Create react app

For this you can use NPM Package create-react-app. If you have already install this app just create new app. For others follow below steps to create your react app.

# npm install -g create-react-app
# create-react-app testApp
# cd testApp
# npm i

Step 2: Install required NPM Packages

Here are are using react Apollo for graphql. Install all required npm packages before going further.

# npm i --save apollo-boost react-apollo apollo-client graphql graphql-tag

Step 3: Create Apollo Mutation

Assume you have setup your own Graphql server that has mutations. We are going to do mutations from client side with Apollo.

First Wrap newly created react app with Apollo Provider

In order to use Apollo Mutations we need to setup Apollo Client. Follow below mentions steps to setup.

First open App.js and replace that code with below code.

./App.js
import React from 'react';
import AddPost from "./addPost"

import ApolloClient from 'apollo-boost';
import {ApolloProvider} from 'react-apollo';

const client = new ApolloClient({
  uri: "http://localhost:4000/graphql"
});

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <AddPost/>
      </div>
    </ApolloProvider>
  );
}

export default App;

Below shows my Graphql server response for mutation that that is going on client side of create react app.

graphql mutation

Let’s Add Apollo Mutation for above shown Grapqhl Mutation.

First of create “addPost.js” file and add below code to it.

import React, { Component } from 'react'
import { Mutation, Query } from 'react-apollo'
import gql from 'graphql-tag'


const ADD_POST = gql`
  mutation AddPost($title:String!, $body:String!,$url:String!, $tags:String!,$summary:String!){
    addPost(title: $title, body: $body,url: $url, tags: $tags, summary:$summary){
      id
      title
      summary
    }
  }
`;

export default class AddPost extends Component {

  state = {
    title: "", 
    body: "",
    url: "", 
    tags: "", 
    summary:""
  }

  handleForm(e){ 
    var name = e.target.name;
    var value = e.target.value;
    this.setState({
      [name]: value
    })
  }
  render() {
    const {title, body, tags, url,summary} = this.state
    return (
      <div className="row">
        <div className="col-md-8 offset-md-2">
          <div className="card">
            <div className="card-header">
              Quote
            </div>
            <div className="card-body">
              <form>
                <div className="form-group">
                  <label htmlFor="title">Title</label>
                  <input type="text"  value={title} className="form-control" id="title" name="title"  placeholder="Enter Title" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="body">Body</label>
                  <input type="text"  value={body} className="form-control" id="body" name="body" placeholder="Enter Body" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="summary">Summary</label>
                  <input type="text"  value={summary} className="form-control" id="summary" name="summary" placeholder="Enter summary" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="url">URL</label>
                  <input type="text"  value={url} className="form-control" id="url" name="url"  placeholder="Enter url" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="tags">Tags</label>
                  <input type="text"  value={tags} className="form-control" id="tags" name="tags" placeholder="Enter tags" onChange={this.handleForm.bind(this)}/>
                </div>
              </form>
              <Mutation mutation={ADD_POST} 
                        variables={{ ...this.state }}>
                {postMutation => <button onClick={postMutation}>Submit</button>}
              </Mutation>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

Component trigger mutations from your UI. To create a Mutation Component, just pass a GraphQL mutation string wrapped with the gql function.

Here I like to separate Mutation component and other html tags. According to Apollo Mutation example they normally wrap whole form inside the Apollo Mutation component. I feel discomfort by doing that way. So I choose this way. Just create the form as usual and wrap the submit button with Mutation component and do the mutation.

Create your GraphQL mutation, wrap it in gql, and pass it to the mutation prop on the Mutation component. The mutate component optionally takes variables, optimisticResponse, refetchQueries, and update. Here we are using “variable” prop only in order to make this example simple.

You can import this component to “App.js” and run. Check whether mutation occur. Let’s add Apollo query and preview result below this form.

Step 4: Adding Query and Previewing result.

Let’s add Apollo Query to display response of the mutation query.  Sometimes when you perform a mutation, your GraphQL server and your Apollo cache become out of sync. This happens when the update you’re performing depends on data that is already in the cache; for example, deleting and adding items to a list. We need a way to tell Apollo Client to update the query for the list of items. This is where the update prop required to update the cache. Aren’t required to update the cache for all mutations, , but our addPost mutation required to update. So we only update the addPost Mutation.

Below shows the posts query used in this example.

add post query graphql
const GET_POSTS = gql`
    query  GetPosts{
      posts{
        id
        title
        summary
      }
    }
`;

/****
 * more codes
 */
export default class AddPost extends Component {

/**More codes */
  }
  render() {
    const {title, body, tags, url,summary} = this.state
    return (
        /**
         *  more codes
         */
        <Mutation mutation={ADD_POST} 
                  variables={{ ...this.state }}
                  update={(cache, { data: { addPost } }) => {
                    const { posts } = cache.readQuery({ query: GET_POSTS });
                    
                    //You can update cache using push and uushift methods. But the
                    //problem is those methods update the existing array and not returning new array
                    //So we have to use concat since concat method returns new array. Apollo only 
                    //renders when it receives new array not a updated array of same ref.
                    cache.writeQuery({
                      query: GET_POSTS,
                      data: { posts: [addPost].concat(posts) },
                    });
                  }}
                  >
          {postMutation => <button onClick={postMutation}>Submit</button>}
        </Mutation>

        <hr/>
        <Query query={GET_POSTS}>
          {({ loading, error, data }) => {
            if (loading) return <h1>Loading...</h1>;
            if (error) return <p>Error :(</p>;
              return data.posts.map((post, key) => {
                return <div key={post.id}>
                  <h3>{post.title}</h3>
                  <p>{post.summary}</p> <hr/>
                </div>
              }) 
          }}
        </Query>
/***
 * more codes
 */

If you try updating a posts, you’ll notice that the UI updates immediately. Even though we don’t plan on using the mutation return result in our UI, we still need to return the id and the property we updated in order for our UI to update reactively. Here, we don’t need to specify an update function since the posts query will automatically reconstruct the query result with the updated todo’s entry in the cache. 

Below shows complete addPost.js File.

import React, { Component } from 'react'
import { Mutation, Query } from 'react-apollo'
import gql from 'graphql-tag'

const GET_POSTS = gql`
    query  GetPosts{
      posts{
        id
        title
        summary
      }
    }
`;

const ADD_POST = gql`
  mutation AddPost($title:String!, $body:String!,$url:String!, $tags:String!,$summary:String!){
    addPost(title: $title, body: $body,url: $url, tags: $tags, summary:$summary){
      id
      title
      summary
    }
  }
`;

export default class AddPost extends Component {

  state = {
    title: "", 
    body: "",
    url: "", 
    tags: "", 
    summary:""
  }

  handleForm(e){ 
    var name = e.target.name;
    var value = e.target.value;
    this.setState({
      [name]: value
    })
  }
  render() {
    const {title, body, tags, url,summary} = this.state
    return (
      <div className="row">
        <div className="col-md-8 offset-md-2">
          <div className="card">
            <div className="card-header">
              Quote
            </div>
            <div className="card-body">
              <form>
                <div className="form-group">
                  <label htmlFor="title">Title</label>
                  <input type="text"  value={title} className="form-control" id="title" name="title"  placeholder="Enter Title" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="body">Body</label>
                  <input type="text"  value={body} className="form-control" id="body" name="body" placeholder="Enter Body" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="summary">Summary</label>
                  <input type="text"  value={summary} className="form-control" id="summary" name="summary" placeholder="Enter summary" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="url">URL</label>
                  <input type="text"  value={url} className="form-control" id="url" name="url"  placeholder="Enter url" onChange={this.handleForm.bind(this)}/>
                </div>
                <div className="form-group">
                  <label htmlFor="tags">Tags</label>
                  <input type="text"  value={tags} className="form-control" id="tags" name="tags" placeholder="Enter tags" onChange={this.handleForm.bind(this)}/>
                </div>
              </form>
              <Mutation mutation={ADD_POST} 
                        variables={{ ...this.state }}
                        update={(cache, { data: { addPost } }) => {
                          const { posts } = cache.readQuery({ query: GET_POSTS });
                          
                          //You can update cache using push and uushift methods. But the
                          //problem is those methods update the existing array and not returning new array
                          //So we have to use concat since concat method returns new array. Apollo only 
                          //renders when it receives new array not a updated array of same ref.
                          cache.writeQuery({
                            query: GET_POSTS,
                            data: { posts: [addPost].concat(posts) },
                          });
                        }}
                        >
                {postMutation => <button onClick={postMutation}>Submit</button>}
              </Mutation>

              <hr/>
              <Query query={GET_POSTS}>
                {({ loading, error, data }) => {
                  if (loading) return <h1>Loading...</h1>;
                  if (error) return <p>Error :(</p>;
                    return data.posts.map((post, key) => {
                      return <div key={post.id}>
                        <h3>{post.title}</h3>
                        <p>{post.summary}</p> <hr/>
                      </div>
                    }) 
                  
                }}
              </Query>
               
            </div>
          </div>
        </div>
      </div>
    )
  }
}

Downloads

Download Complete example. Note: graphql server not included.

Download Full example – Apollo Mutation.

OR

Download only changed files on create react app.

  1. Download AddPosts.js
  2. Download App.js

Leave a Reply

avatar
  Subscribe  
Notify of