Introduction

In this tutorial we will integrate a React client into our GraphQL API with the help if the Apollo client package. We will build an interface to perform the four CRUD actions on the database. The main focus of this tutorial is on the GraphQL and Apollo portions. If you are new to React I still recommend doing this tutorial. I won't be explaining the basic React concepts like props and state but you'll able to see them in action. Then you can go back and build a tic tac toe game like they do in their introductory tutorial.


Setup

Create-react-app

You need to have Node and Yarn installed on your computer to use React. We will install and use create-react-app to get us going.

Create-react-app is a Node package developed by Facebook that gives you a starter app out of the box. It includes an already configured Webpack server that bundles all the JavaScript files in the app into a bundle.js file that gets attached into the index.html file at runtime. At runtime, Webpack’s dev server will listen for you to save changes in develepment, and will automatically load your running web page with the changes. This is called hot reloading. Install it as a Node global package:

  • npm install -g create-react-app
  • Yarn and npm (Node Package Manager) are both package managers for managing JavaScript dependencies. You can use either, but since React and Yarn were both created by Facebook, they tend to interact tightly together so we will be using Yarn to install packages and run the server. Install it globally if it's not already installed.

  • npm install -g yarn
  • We already created a GraphQL API that we will be using as our back end. We could keep the front end and back end code in completely separate locations, but if we are to deploy our application in production as an integrated web application it's easier to keep them together. Use the below command from your API project's root directory to add a directory called client, place a preconfigured React application there, and cd into it.

  • create-react-app client && cd client
  • Run the server to make sure it's working:

    yarn start

    You should see the default Welcome to React app in your browser at localhost:3000.


    client/package.json

    The create-react-app generator created our package.json file. So now we have two of those. One for our API application and one for our React client. The generator installed the react, react-dom, and react-scripts packages. They will be listed as dependencies in the package.json file.

    Before you install the other packages you will be using let's make one change to the package.json file. Add a proxy property set to your API's port. This will automatically be added to the API routes you will call from the React client, so you only have to use the path, not the full URL in your code. That may not seem like much of a benefit since you only have to access one route on a GraphQL API. But doing so will help integrate the client with the API as if it was one application, preventing the cross domain access issues discussed in Part 1 of this tutorial. This makes it easier to deploy the app to a platform like Heroku.

    package.json
    {
      "name": "client",
      "version": "0.1.0",
      "private": true,
      "proxy": "http://localhost:4000",
      "dependencies": {
      ...
    }
    

    Install the additional packages we will need for our React app:

    yarn add react-router-dom bootstrap graphql react-apollo apollo-boost

    GraphQL and Apollo

    Let's talk about the GraphQL-related packages we installed.

    Graphql: GraphQL servers are available in over a dozen different programming languages. The graphql package is its implementation in JavaScript.

    Apollo is a software company that provides a number of tools and services around the GraphQL query language. You don't have to use Apollo packages to use GraphQL with React, but it adds some advantages.

    Apollo-boost: Apollo Client facilitates working with GraphQL data on the client side. The apollo-boost package installs and configures apollo-client and other apollo packages as its dependencies. This includes managing a central store of data which can either replace or at least compliment the Redux store. Apollo-boost is most commonly paired with React but it can be used with Angular or Vue as well.

    While you can use both Apollo and Redux to manage your React client's central store, it is best to use one or the other so you don't have two sources of truth. Apollo provides some useful functionality that you would have to code yourself with Redux. It automatically adds GraphQL query results to the store. You can store local data with Apollo as well.

    React-apollo: connects the graphql and apollo-boost libraries with your React code. We will be using ApolloProvider, Query, and Mutation components from the react-apollo library.


    File Structure

    If you look at the files and directories generated in the client directory, besides a folder for the node_modules you'll see a "public" and a "src" directory. These are where you add your own files. Right now there are no sub-directories in those folders so things can get pretty cluttered quickly if you don't add some structure. From the client directory you can use these UNIX commands to add and remove the relevant directories and files. Or do it from your text editor if you prefer.

    rm README.md
    rm -rf .git
    mkdir -p src/components/pages
    touch src/components/pages/Home.js
    mkdir src/components/articles
    touch src/components/articles/ArticleList.js
    touch src/components/articles/ArticleInfo.js
    touch src/components/articles/ArticleAdd.js
    touch src/components/articles/ArticleEdit.js
    mkdir src/graphql
    touch src/graphql/articleQueries.js
    

    We removed some unnecessary files including the git repository that create-react-app generated for us. We won't be using git in this tutorials but if we were we would generate a git repository in the project root directory for the API and React client combined. You can leave the .gitignore file though since git allows multiple gitignore files.

    We created directories for our components. And we created a directory called graphql with a file called articleQueries. This is just so you can have the API query and mutation code in one location for purposes of this tutorial.

    Stylesheets

    The index.css file is for our app-wide css. If you want to add custom CSS classes that apply to the whole app this is where you would do it. The App.css file is specific to the App.component we are going to fill out. Delete the CSS that's in there now so it doesn't conflict with your app.

    src/app.css
    /* Remove all the CSS classes */
    

    To add css classes that apply only to a particular component you could create a css file in the same folder as that component and import it in the component(s) that use it.

    Bootstrap: For convenience we're using Bootstrap for our styling. We installed the bootstrap package earlier. Now import it to the index.js file.

    src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import 'bootstrap/dist/css/bootstrap.css';
    import './index.css';
    import App from './App';
    ...
    

    If you want to use the JavaScript features of Bootstrap like drop-down menus you can also install the react-bootstrap package, be we won't use those in this tutorial.


    Public/index.html - Our Single Page App

    This is a Single-Page Application (SPA) with CRUD capabilities. It is single page because you only have one html file sitting in the public folder: public/index.html. It has your standard HTML page structure with one element inside the body, an empty div tag with an id named root <div id="root"></div>. That is where React renders it's output with JavaScript. There's nothing special about the name "root." It can be any name. We won't make any changes to this file.


    Src/index.js File

    In the src/index.js file we import the React library and render our main component. Other than adding the Bootstrap import above, we don't need to make any changes to the src/index.js file. It should look like this.

    src/index.js
    1. import React from 'react';
    1. import ReactDOM from 'react-dom';
    3  import 'bootstrap/dist/css/bootstrap.css';
    4  import './index.css';
    1. import App from './App';
    6  import * as serviceWorker from './serviceWorker';
    7 
    2. ReactDOM.render(<App />, document.getElementById('root'));
    3. serviceWorker.unregister();
    
    1. We import the React and ReactDOM libraries, and the App.jsx file (containing the App component).
    2. The ReactDOM module's render method is what renders your React components to the public/index.html page. It takes two arguments. The first calls our component which is a custom React element called "App", or whatever name you want to give it. The second argument is the target element where we will render the output from <App />. In this case the element with the id of "root".
    3. The service worker is a web API that helps you cache your assets and other files so that when the user is offline or on a slow network they can still see results on the screen. It is actually not activated by default so we're leaving it that way as well (change .unregister to .register to make it active).

    React Components

    With React you split your user interface into components. Each component returns some JSX which ultimately gets rendered as HTML to the user interface. In our case we are creating the front end to a CRUD application. We need components to correspond with the API's endpoints to perform the CRUD actions.

    A few notes on syntax. We can use all the latest JavaScript syntax without worrying about browser compatibility because create-react-app installed Babel which compiles our JavaScript into ES5. We did not add the step of installing Babel in our API, so while we could still use most of the ES6 syntax, there are some features not yet supported by Node like the Import and Export syntax. But we can and will be using those here. Also, in general I am using the declaration syntax for functions rather than arrow functions just to make it explicit when you are returning a value. But there is no reason not to use arrow functions if you prefer it.

    The App.js component file holds the structure for our web page. The ArticleList, ArticleInfo, ArticleAdd, and ArticleEdit components represent views that correspond to the Read, Read and Delete, Create, and Update actions respectively. We'll also add a Home component to be our home page. Note that component names must be capitalized. In each file we will build one React component, with the exception of App.js where we build three. We could break it out even further, like a form component that gets imported to the ArticleAdd and ArticleEdit components, but we won't.

    Home Component

    Before we build our full CRUD app let's start out with a very simple component called Home. We already created an empty Home.js file. Populate it with the below:

    src/components/pages/Home.js
    1. import React from 'react'; 
     2 
    2. function Home() { 
     4   return (
     5     <div className="jumbotron">
     6       <h1>Home Page</h1>
     7     </div>
     8   );
     9 }
    10 
    3. export default Home;
    
    1. Import React.
    2. Create a component. You can create components with functions or classes. In general, if you don't have to make it a class then don't. This component simply returns some JSX. JSX looks like HTML but it actually isn't. Not yet anyway. You'll notice subtle differences between JSX and HTML like you have to use the className attribute instead of class since "class" is a reserved word in JavaScript. But React ultimately converts JSX to HTML through its ReactDOM.render method.
    3. Export the component. In the next section we will import it in App.js.

    Right now this file is just sitting in space not connected to anything. If you want to see it in action you can temporarily open the src/index.js file and change the App elements to Home. Then go to your browser and it should now say "Home Page."

    # src/index.js
    ...
    import Home from './components/pages/Home';
    ...
    ReactDOM.render(<Home />, document.getElementById('root'));
    

    Undo those index.js file changes back to the way it was (referencing "App").


    App component

    The term Single-Page App can be a little misleading. While it is indeed a single html page, that doesn't mean you can't have multiple views with changes to the URL. We will use the React Router DOM package that we installed to do just that. And we will also perform the standard CRUD actions in our single page interacting with the API to get articles, post new articles, edit existing articles, and delete articles. Instead of doing those things all on separate HTML pages as you would in an traditional web app, we will do it from our single page sending queries and mutations to the API with the help of the GraphQL and Apollo packages we installed. React will move the data around using JavaScript which can add significant speed over entire page loads from the server.

    The central component for our app, generated by create-react-app is called "App." That is what was returning the Welcome to React page we saw in the browser. Replace that with a Navigation bar and a place to render the other components. Here is the code for the App.js file all at once:

    src/App.js
    import React from 'react';
    import ApolloClient from 'apollo-boost';
    import { ApolloProvider } from 'react-apollo';
    import { BrowserRouter as Router, Route, NavLink, Switch } from 'react-router-dom';
    import Home from './components/pages/Home';
    import ArticleList from './components/articles/ArticleList';
    import ArticleInfo from './components/articles/ArticleInfo';
    import ArticleAdd from './components/articles/ArticleAdd';
    import ArticleEdit from './components/articles/ArticleEdit';
    
    const client = new ApolloClient({
      uri: '/graphql'
    });
    
    function App() {
      return (
        <ApolloProvider client={client}>
          <Router>          
            <Navigation />
            <div className="container">
              <Main />
            </div>
          </Router>
        </ApolloProvider>
      );
    }
    
    function Navigation() {
      return(
        <nav className="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
          <div className="container">
            <ul className="navbar-nav mr-auto">
              <li className="nav-item"><NavLink exact className="nav-link" activeClassName="active" to="/">Home</NavLink></li>
              <li className="nav-item"><NavLink exact className="nav-link" activeClassName="active" to="/articles">Articles</NavLink></li>
            </ul>
          </div>  
        </nav>
      );
    }
    
    function Main() {
      return(
        <Switch>
          <Route exact path="/" component={Home} />
          <Route exact path="/articles" component={ArticleList} />
          <Route exact path="/articles/new" component={ArticleAdd} />
          <Route exact path="/articles/:_id" component={ArticleInfo} />
          <Route exact path="/articles/:_id/edit" component={ArticleEdit} />
        </Switch>
      );
    }
    
    export default App;
    

    First thing's first. Once you add the code and save the file, go to the browser and make sure you didn't get any errors. We removed the Welcome to React header and logo and replaced it with a navigation bar with two links, and a simple home page jumbotron. There is nothing in the articles link yet but we'll add that shortly.

    If you are familiar with React you may notice that we are using functional components rather than class components. That's because we are using Apollo Client to manage our state which it can do in a functional component. The React ecosystem is moving away from class components where they can, so we'll only be using functional components in this app. If you are new to React just bask in the happiness that you don't have to get bogged down with componentDidMount... or was it componentWillMount, no it was componentMightHaveMounted. Whatever, let's move on.

    This file actually contains three separate components... App, Navigation, and Main. But most of this code relates to Navigation and the React Router package. React Router is the most popular Routing package. The docs are at reacttraining.com/react-router/web/guides which looks like a third party training site, and it is. But they are the ones who created the React Router package (not Facebook's React team).

    Alright, let's break this code down in sections. We'll start with the React import:

    import React from 'react';
    

    To create a component we need to import the React library.

    Create an instance of the Apollo Client

    import ApolloClient from 'apollo-boost';
    ...
    const client = new ApolloClient({
      uri: '/graphql'
    });
    

    Import ApolloClient from the apollo-boost libraries then create an instance of it assigned to variable "client". Pass in an object with a uri property. This is the url to our GraphQL API endpoint. We only put the path here because we added the domain and port as a proxy in the client/package.json file.

    The App component:

    import { ApolloProvider } from 'react-apollo';
    import { BrowserRouter as Router, ... } from 'react-router-dom';
    ...
    function App() {                      #1
      return (
        <ApolloProvider client={client}>  #2
          <Router>                        #3 
            <Navigation />
            <div className="container">
              <Main />
            </div>
          </Router>
        </ApolloProvider>
      );
    }
    
    1. App is the top level component of our Application.
    2. The first thing we are doing is connecting Apollo Client to the React app by wrapping it in the ApolloProvider component imported from react-apollo. And we pass our client to the client property. This allows us to access the client from any component in our component tree.
    3. We imported the Router element from react-router-dom to manage the app's routing. It in turn contains two custom components that call the corresponding components defined below -- Navigation and Main. There is nothing special about those names, you could call them what you like. Those components return JSX that gets inserted into the App component.

    The Navigation component:

    import { ... NavLink,... } from 'react-router-dom';
    ...
    function Navigation() {                              #1
      return(
        <nav className="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
          <div className="container">
            <ul className="navbar-nav mr-auto">
              <li className="nav-item"><NavLink exact className="nav-link" activeClassName="active" to="/">Home</NavLink></li>  #2a
              <li className="nav-item"><NavLink exact className="nav-link" activeClassName="active" to="/articles">Articles</NavLink></li>  #2b
            </ul>
          </div>  
        </nav>
      );
    }
    
    1. The Navigation component returns JSX that ultimately renders the nav bar using the Bootstrap classes we provide here.
    2. The React Router NavLink component is a subset of a Link component that we'll use shortly. It provides the activeClassName property to style the link differently when it's active. The other thing to note is the "exact" attribute, which is the shorthand for "exact=true." That means the route has to be the exact route provided with the "to" attribute. The default is that it just contains the route provided. So the "/" route by default would include any route that contains "/", which is all routes. So we need the exact attibute here.

    The Main component:

    import { ... Route, ... Switch } from 'react-router-dom';
    import Home from './components/pages/Home';                  #1
    import ArticleList from './components/articles/ArticleList';
    import ArticleInfo from './components/articles/ArticleInfo';
    import ArticleAdd from './components/articles/ArticleAdd';
    import ArticleEdit from './components/articles/ArticleEdit';
    ...
    function Main() {                                            #2
      return(
        <Switch>                                                 #3
          <Route exact path="/" component={Home} />              #4
          <Route exact path="/articles" component={ArticleList} />
          <Route exact path="/articles/new" component={ArticleAdd} />
          <Route exact path="/articles/:_id" component={ArticleInfo} />
          <Route exact path="/articles/:_id/edit" component={ArticleEdit} />
        </Switch>
    );
    
    1. Import the components that you will be calling with your Routes. Each of those files will contain a component. Each of the articles components corresponds with one or more endpoints in our API.
    2. The Main component is where you insert all your Route elements.
    3. The React Router switch statement works like a JavaScript switch statement. It checks each statement below it in order until there is a match.
    4. Route is a React Router element that takes as attributes the path and the component to call if there is a match. The "exact" attribute requires the match be exact.

    GraphQL Queries and Mutations

    When we built the GraphQL API, we tested it with queries and mutations to each of our endpoints using the graphql playground (if you used apollo-server) or graphiql tool (if you used express-graphql) in the browser. We'll send those same queries and mutations from our React client. Let's put them all in one file for convenience:

    src/graphql/articleQueries
     1 import { gql } from 'apollo-boost';
     2 
     3 const GET_ARTICLES = gql`
     4   {
     5     articles {
     6       title
     7       content
     8       id
     9     }
    10   }
    11 `;
    12 
    13 const GET_ARTICLE = gql`
    14   query article($id: ID!) {
    15     article(id: $id) {
    16       id
    17       title
    18       content
    19     }
    20   }
    21 `;
    22 
    23 const CREATE_ARTICLE = gql`
    24   mutation createArticle($title: String!, $content: String!) {
    25     createArticle(articleInput: { title: $title, content: $content }) {
    26       title
    27       content
    28       id
    29     }
    30   }
    31 `;
    32 
    33 const DELETE_ARTICLE = gql`
    34   mutation deleteArticle($id: ID!) {
    35     deleteArticle(id: $id) {
    36       title
    37       content
    38       id
    39     }
    40   }
    41 `;
    42 
    1. const UPDATE_ARTICLE = gql` 
    2.   mutation updateArticle($id: ID!, $title: String!, $content: String!) {     
    3.     updateArticle(id: $id, articleInput: { title: $title, content: $content }) { 
    4.       id
    47       title
    48       content
    49     }
    50   }
    51 `;
    52 
    53 export { GET_ARTICLES, GET_ARTICLE, CREATE_ARTICLE, DELETE_ARTICLE, UPDATE_ARTICLE };
    

    We have one query or mutation string per endpoint on our API. I won't go through each of these individually but rather go over the structure using UPDATE_ARTICLE as an example from the inside out.

    1. Each query or mutation string is wrapped with in a gql function imported from apollo-boost. We are assigning them to constants in all caps which is the format Apollo recommends.
    2. Tell the GraphQL API whether this is a Query or Mutation (or Subscription, or a custom type). Start by filtering the variables we are passing in to check their type and whether any required fields are missing. In this case case the variables are ID type and String type. The ! means the field is required.
    3. Now pass in the tested variables to the function that gets sent to the API. The name of each query/mutation and arguments align with the resolver function names and arguments on the API.
    4. List the properties you want returned back from the API, in this case we want id, title, and content.

    ArticleList component

    In our app's navbar is a link to "articles" which loads our ArticleList component. Let's populate it.

    src/components/articles/ArticleList.js
     1 import React from 'react';
     2 import { Link } from 'react-router-dom';
     3 import { Query } from 'react-apollo';
     4 import { GET_ARTICLES } from '../../graphql/articleQueries';
     5 
     6 function ArticleList() {
     7   return(
     8     <div>
     9       <h2>
    10         Articles
    1.         <Link to="/articles/new" className="btn btn-primary float-right"> 
    12           Create Article
    13         </Link> 
    14       </h2>
    2.       <Query query={GET_ARTICLES}> 
    3.         {function({ loading, error, data }) { 
    17           if (loading) return "Loading...";
    18           if (error) return `Error! ${error.message}`;
    4.           const { articles } = data; 
    20           return (
    5.             articles.map(function(article) { 
    22               return(
    6.                 <div key={article.id}> 
    24                   <hr/>
    25                   <h4><Link to={`/articles/${article.id}`}>{article.title}</Link></h4>
    26                   <small>id: {article.id}</small>
    27                 </div>
    28               )     
    29             })
    30           );
    31         }}
    32       </Query>
    33     </div>
    34   )
    35 }
    36 
    37 export default ArticleList;
    
    1. We imported the Link component from react-router-dom to go to the articles/new route. And in our articles list, each article name has a link to the articles/:id route.
    2. We imported a react-apollo component called Query that we are using here. We also imported the GET_ARTICLES query defined in the graphql/articleQueries file and are passing it in as props. Apollo will handle the sending and retrieving of the data to and from the API. If we were to do it ourselves we would send a fetch query or use the Axios package and wait for then handle the response.
    3. Apollo can provide different states for loading, error response, or data from a successful response.
    4. Unpacking the articles array from the data response object makes it a little easier to work with. Data is similar to working with props or state in standard React.
    5. We'll use the JavaScript map function to return each article object displayed how we like. In this case with the article title that links to the article, and the article id just to help us in development.
    6. In React, when you iterate over an array of objects like we are here, you need to assign a unique key to each one. We have the id field for each article so we'll use that.

    Now let's test it out. Make sure the API server is running. Open another window and go to the project's root directory. Then run:

  • nodemon
  • Your API server should now be running on localhost:4000. If you are using a local version of MongoDB also make sure that is running in a separate window (mongod). You may need to restart your Client server. Go back to the window running the React app. Stop the server with CTRL+C. Then run:

  • yarn start
  • And now your front end React app should be running on localhost:3000 and should be displaying the articles saved to your database.


    ArticleInfo component

    On the articles page in your browser if you click on an article title it should take you to the /articles/:id route and the ArticleInfo component. It will be empty because we haven't entered any code yet. So let's do that now:

    src/components/articles/ArticleInfo.js
     1 import React from 'react';
     2 import { Link } from 'react-router-dom';
     3 import { Query, Mutation } from 'react-apollo';
     4 import { GET_ARTICLE, DELETE_ARTICLE, GET_ARTICLES } from '../../graphql/articleQueries';
     5 
     6 function ArticleInfo(props) {
     7   return (
    1.     <Query query={GET_ARTICLE} variables={{ id: props.match.params._id }}>  
     9       {function({ loading, error, data }) {
    10         if (loading) return "Loading...";
    11         if (error) return `Error! ${error.message}`;
    12         const { article } = data;
    13         return (
    14           <div>
    15             <h2>{article.title}</h2>
    16             <small>id: {article.id}</small>
    17             <p>{article.content}</p>
    18             <p className="btn-group">
    19               <Link to={`/articles/${article.id}/edit`} className="btn btn-info">Edit</Link> 
    2.               <Mutation mutation={DELETE_ARTICLE} > 
    3.                 {function(deleteArticle, { data }) { 
    22                   return(
    23                     <button className="btn btn-danger"
    4.                       onClick={() => { 
    5.                         deleteArticle({ 
    6.                           variables: { id: article.id }, 
    7.                           refetchQueries: [{query: GET_ARTICLES}] 
    28                         });
    8.                         props.history.push("/articles"); 
    30                       }}
    31                     >
    32                       Delete
    33                     </button> 
    34                   )
    35                 }}
    36               </Mutation>
    37               <Link to="/articles" className="btn btn-secondary">Close</Link>
    38             </p>
    39             <hr/>
    40           </div>
    41         );
    42       }}
    43     </Query>
    44   )
    45 }
    46 
    47 export default ArticleInfo
    
    1. This is similar to ArticlesList in that we import a Query component from react-apollo and pass a query string we imported from the graphql/articleQueries file as the query property. But this time we also include the variables object and pass in an id property. We get that from a React object called props. Props.match.params._id will pull the _id value from the url. MongoDB calls it's autogenerated id field _id, which technically follows a JavaScript convention around private property names.

    Now let's dig into mutations. On the article page we will have a button to delete the article. When the user presses the button we need to send the deleteArticle mutation to the GraphQL API with the article.id property. We imported the Mutation component from react-apollo.
    In the graphql/articleQueries file we created a GraphQL mutation string calling deleteArticle, wrapped it with a gql function, assigned it to the DELETE_ARTICLE constant, and imported it to our current ArticleInfo component.

    1. To create a Mutation component we must pass the mutation string as a mutation property.
    2. We must also provide a function to the props.children that tells React what to render. In this case it is the delete button.
    3. We are putting the onClick handler function directly in the button component.
    4. It calls deleteArticles taking an object as its argument.
    5. The first property is any variables we want to pass to the API endpoint. In this case we are passing the article.id.
    6. The second property we are adding is refetchQueries. When we delete the article we want it removed from our articles list. The easiest way to do that is just to call a new query. You could also modify the cached articles array which would avoid another API call. To do that, replace statement 7 with the below. This uses the Apollo update function.
    7. update(cache, { data: { createArticle } }) {
        const { articles } = cache.readQuery({ query: GET_ARTICLES });
        cache.writeQuery({
          query: GET_ARTICLES,
          data: { articles: articles.filter(a => (a.id !== article.id)) },
        });
      }
      
    8. Finally we redirect to the articles page.

    Try viewing and deleting an article in the browser. We'll cover creating an article next. You can add back articles using the GraphQL playground at localhost:4000 (if using apollo-server) or the Graphiql tool in the browser at localhost:4000/graphql (if using express-graphql).

    mutation {
      createArticle(articleInput: { title: "Learn React", content: "Lorem Ipsum."}) {
        id, title, content
      }
    }
    

    ArticleAdd component

    On the articles page in your browser there is a button that will take you to the /articles/new route and the ArticleAdd component. This displays a form to create a new article.

    src/components/articles/ArticleAdd.js
     1 import React from 'react';
     2 import { Link } from 'react-router-dom';
     3 import { Mutation } from 'react-apollo';
    1. import { CREATE_ARTICLE, GET_ARTICLES } from '../../graphql/articleQueries'; 
     5 
     6 function ArticleAdd(props) {
     7   let title, content;
     8 
     9   return (
    2.     <Mutation mutation={CREATE_ARTICLE}> 
    3.       {function(createArticle, { data }) { 
    4.         return( 
    13           <div>
    14             <h4>Add Article</h4><hr/>
    15             <form
    5.               onSubmit={function(event) { 
    6.                 event.preventDefault(); 
    7.                 createArticle({
    8.                   variables: { title: title.value, content: content.value }, 
    9.                   refetchQueries: [{query: GET_ARTICLES}] 
    20                 });
    10.                props.history.push("/articles"); 
    22               }}
    23             >
    24               <div className="form-group">
    25                 <label>Title:</label>
    26                 <input type="text" className="form-control" 
    11.                  ref={function(node) { return title = node; }} 
    28                 />              
    29               </div>
    30               <div className="form-group">
    31                 <label>Content:</label>
    32                 <textarea rows="5" className="form-control"
    33                   ref={function(node) { return content = node; }} 
    34                 />
    35               </div>
    36               <p className="btn-group">
    37                 <button type="submit" className="btn btn-primary">Submit</button>
    38                <Link to="/articles" className="btn btn-secondary">Cancel</Link>
    39               </p>
    40             </form>
    41           </div>
    42         )}
    43       }
    44     </Mutation>
    45   );
    46 };
    47 
    48 export default ArticleAdd;
    
    1. We will be using the mutation and query strings assigned to CREATE_ARTICLE and GET_ARTICLES.
    2. Creating an article is a write action so we will use a Mutation component imported from the react-apollo library. And we will pass the mutation defined in CREATE_ARTICLE.
    3. The Mutation component requires a function that takes the endpoint name and data being submitted as arguments.
    4. The function returns the JSX that React will render.
    5. We are putting the onSubmit handler function directly in the form element.
    6. As with standard AJAX form submissions, we need to prevent the default behavior when a form is submitted.
    7. It calls createArticle taking an object as its argument.
    8. The first property are the variables we want to pass to the API endpoint. In this case we are passing the values from the form fields.
    9. The second property we are adding is refetchQueries. When we add the article we want it added to our articles list. The easiest way to do that is just to call a new query. Alternatively you could modify the cached articles array to avoid another API call. To do that replace statement 9 with the below code:
    10. update(cache, { data: { createArticle } }) {
        const { articles } = cache.readQuery({ query: GET_ARTICLES });
        cache.writeQuery({
          query: GET_ARTICLES,
          data: { articles: articles.concat([createArticle]) },
        });
      }
      
    11. Finally we redirect to the articles page.
    12. The React input element is a DOM node. To access it's content we are using the React ref property.

    Test it out by adding an article in the browser.


    ArticleEdit component

    We are now on the last component in our React front end, the edit page to Update an existing article in the database. On the article info page in your browser there is an Edit button that will take you to the /articles/:id/edit route and the ArticleEdit component. This is another form like ArticleAdd, but we need to populate it with the article's existing values.

    src/components/articles/ArticleEdit.js
     1 import React from 'react';
     2 import { Query, Mutation } from 'react-apollo';
     3 import { GET_ARTICLE, UPDATE_ARTICLE } from '../../graphql/articleQueries';
     4 
     5 function ArticleEdit(props) {
     6   function handleCancel(id) {
     7     props.history.push(`/articles/${id}`);
     8   }
     9 
    10   let title, content;
    11   return (
    12     <Query query={GET_ARTICLE} variables={{ id: props.match.params._id }}>
    1a.      {function({ loading, error, data }) {
    14         if (loading) return "Loading...";
    15         if (error) return `Error! ${error.message}`;
    16         const { article } = data;
    17         return (
    18           <div>
    19             <h1>Edit {article.title}</h1>
    20             <Mutation mutation={UPDATE_ARTICLE}>
    1b.              {function(updateArticle, { loading, error }) { 
    22                 return( 
    23                   <div>
    24                     <form
    25                       onSubmit={function(event) {
    26                         event.preventDefault();
    2.                         updateArticle({ 
    28                           variables: {
    29                             id: article.id,
    30                             title: title.value,
    31                             content: content.value
    32                           }
    33                         });
    34                         props.history.push(`/articles/${article.id}`);
    35                       }}
    36                     >
    37                       <div className="form-group">
    38                         <label>Title</label>
    39                         <input type="text" className="form-control"
    3.                           defaultValue={article.title} 
    41                           ref={function(node) { return title = node; }} />
    42                       </div>
    43                       <div className="form-group">
    44                         <label>Content</label>
    45                         <textarea rows="5" className="form-control"
    46                           defaultValue={article.content} 
    47                           ref={function(node) { return content = node; }} />
    48                       </div>
    49                       <div className="btn-group">
    50                         <button type="submit" className="btn btn-primary">Update</button>
    51                         <button type="button" className="btn btn-secondary" 
    52                           onClick={function() { handleCancel(article.id) }}>Cancel</button>
    53                       </div>
    54                     </form>
    55                     {loading && <p>Loading...</p>}
    56                     {error && <p>Error : {error.message}</p>}
    57                   </div>
    58                 )
    59               }}
    60             </Mutation>
    61           </div>
    62         );
    63       }}
    64     </Query>      
    65   )
    66 }
    67 
    68 export default ArticleEdit;
    
    We are using the same concepts used in ArticleInfo for the Query component and ArticleAdd for the Mutation component and form. So I will only point out what is new or different.
    1. We have two potential loading or error responses. One on the initial GET_ARTICLE query, and one on the UPDATE_ARTICLE mutation.
    2. UpdateArticle() is a mutate function. It takes an options object as its argument. We are using the variables option to pass the form data for the update. What is different from the createArticle() mutate function in the ArticleAdd's Mutation component is we don't have to add any options (i.e., update or refreshQueries) to update our cache. Apollo will take the response from the API and update the cache for us behind the scenes.
    3. To populate the form fields with the existing values we are using React's defaultValue property.

    Test it out by editing an article in the browser.

    This completes our last component. There are two more brief topics, both optional, then we are done.


    Apollo Client Developer Tool for Chrome Browsers

    Apollo has created a plug-in for Chrome Developer Tools that is pretty useful. You can download it free at chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm


    Run the API and React Client with one command

    Right now we need separate terminal windows open to run our back end API server (port 4000) and our front end React app (port 3000). Not to mention our MongoDB server. For convenience we can run our API and React apps with one command using the Concurrently package. Make sure you are in the project's root directory (not in the client folder). Using the --save-dev flag installs it as a development environment dependency.

  • npm install concurrently --save-dev
  • Open the package.json file in the project's root directory (not the one in the client folder) and add the highlighted script below. Note that you need to add a comma after the start script.

    // package.json
      ...
      "scripts": {
        "start": "node server.js",
        "dev": "concurrently \"nodemon server.js\" \"cd client && npm run start\""
      },
      ...
      "devDependencies": {
        "concurrently": "^4.1.1"
      },
    

    This will run the nodemon command on the API application starting the server on port 4000. Then it will cd to the client directory and run the start command which will run the React app on port 3000. To execute this script, make sure you stop both servers first, then from the project's root directory run:

  • npm run dev
  • This should start both servers and open the React app in your browser.


    You now have a fully functioning GraphQL app using Node.js, MongoDB, and React with all four CRUD capabilities. That's a pretty advanced stack. Not bad.