An Introduction to GraphQL — Part 3 — Client in React and Apollo

An Introduction to GraphQL — Part 3 — Client in React and Apollo
NASA

Originally posted on Medium - 5th May 2019

So now we’ve seen the server side of things, it’s time to play on the client side. This will be a very basic React application using Apollo as its GraphQL library. Unfortunately there is a lot of boilerplate to get this set up, but once it’s going, it’s very easy to extend.

So we’ll start with a very basic “create-react-app” project in Typescript.

create-react-app react-apollo --typescript
cd react-apollo
yarn add apollo apollo-boost graphql graphql-tag react-apollo

We’ve added the various Apollo libraries we need along with the GraphQL libraries to support it all.

Next, lets modify our package.json with some handy scripts

"updateSchema": "apollo schema:download --endpoint http://localhost:8090/graphql schema.json",
"generateTypes": "apollo client:codegen --target typescript --no-addTypename --localSchemaFile=schema.json"

First things first, let’s grab our schema, just run “yarn updateSchema” (make sure your server that we wrote in the last part of this article is running before this).

This will grab the schema into a json file into our project. Run this whenever you update your server as it’s what Apollo uses to generate our classes from.

Ok, modify your App.tsx to be the following

import React, { Component } from 'react';
import './App.css';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import Username from './Username';

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


class App extends Component {
  render() {
    return (
      <ApolloProvider client={client}>
        <div className="App">
          <Username />
        </div>
      </ApolloProvider>
    );
  }
}

export default App;

What we are doing here is defining which server our client talks to, defining the provider component (We want this at the top level so we can access Apollo from any component) and adding a Username component. We’ll define that shortly.

Personally, I like to keep my queries (and mutations) in seperate folders within the project, so create a “queries” folder and define your username GraphQL query.

import gql from "graphql-tag";

export default gql`
query UsernameQuery {
    user {
        name
    }
}
`;

Pretty basic query, feel free to extend if you’d like more information in your project, but this is a good starting place. You’ll also notice we’ve named the query itself to be “UsernameQuery”, this comes in with the next step, auto generating code. It will define the names of the interfaces generated.

From a command line, run the following

yarn generateTypes

This tells Apollo to go off, look for all the GraphQL queries defined, and generate type definitions for our responses (and requests where we take variables).

You’ll notice that we have a bunch of “__generated__” folders now with interfaces defined in them. This makes our lives a whole lot easier when building our components.

So let’s define our Username component now

import React from 'react';
import { Query, QueryResult } from 'react-apollo';
import { UsernameQuery } from './queries/__generated__/UsernameQuery';
import USERNAME_QUERY from './queries/username';

export default () => {
    return (
        <Query query={USERNAME_QUERY}>
            {({ loading, error, data }: QueryResult<UsernameQuery>) => {
                if (loading) return <p>Loading...</p>;
                if (error) return <p>Error :(</p>;

                return <div>Hello {data!.user!.name}</div>
            }}
        </Query>
    );
}

You’ll notice in this example, I’m ignoring null checks in our data. As our Schema is defined with these being optional, they can be null. Feel free to add these in, I’ve just left them out for readability.

You’ll see this is all relatively easy. We wrap our component in a Query and pass it the query we want to call. It returns us “loading”, “error” and “data” values so we can react accordingly. Then our ultimate response is just showing “Hello <username>”.

And that’s all there is to a basic client.


All of that is pretty simple, but ultimate fairly useless as we can’t interact with our data, so let’s extend this to mutations with variables (a lot of these concepts extend to queries also).

Create a new folder called “mutations” (some people may like to just keep these with their queries) and add the following file.

import gql from 'graphql-tag';

export default gql`
    mutation LatestPostMutation($newPostVal: String!) {
        latestPost(newPost: $newPostVal)
    }
`;

So a new concept here of variables. we give our mutation a name of LatestPostMutation and define it with a string (non null) variable $newPostVal. Then as you can see, we pass it to the latestPost field in our query (as newPost).

Now re-run your generateTypes script

yarn generateTypes

Once again, we have some new “__generated__” folders defined. If you have a look at the one under mutations, you’ll see a single file. Open that and there are two interfaces defined (our queries only had the one). We have a proper definition of the variables we have to send to the server.

These next steps can look a little messy, but it has a lot of future expandability. We’re dealing with the Apollo cache here in updating it with the data we’ve set.

We could do this without the wrapper component I’m about to define, but this comes in handy if you want a display component, and a seperate edit component in the future (beyond the scope of this article, but should be a good exercise for you to play with). So let’s define a query (put the first file in the “queries” folder) and our component as we did in the previous section of this article.

import gql from 'graphql-tag';

export default gql`
    query LatestPostQuery {
        latestPost
    }
`;
import React from 'react';
import { Query, QueryResult } from 'react-apollo';
import { LatestPostQuery } from './queries/__generated__/LatestPostQuery';
import LATEST_POST_QUERY from './queries/latestPost';
import UpdateLatestPost from './UpdateLatestPost';

export default () => {
    return (
        <Query query={LATEST_POST_QUERY}>
            {({ loading, error, data }: QueryResult<LatestPostQuery>) => {
                if (loading) return <p>Loading...</p>;
                if (error) return <p>Error :(</p>;
                // Show latest post in an update field
                return <span><UpdateLatestPost latestPost={data!.latestPost} /></span>
            }}
        </Query>
    );
}

Nothing you haven’t seen before. Time to do the big one, the UpdateLatestPost component

import React from 'react';
import { Mutation } from 'react-apollo';
import LATEST_POST_MUTATION from './mutations/latestPost';
import LATEST_POST_QUERY from './queries/latestPost';
import { LatestPostMutation, LatestPostMutationVariables } from './mutations/__generated__/LatestPostMutation';

export interface UpdateLatestPostProps {
    latestPost: string;
}

export default (props: UpdateLatestPostProps) => {
    let latestPostData: {
        latestPostField: HTMLInputElement | null;
    } = {
        latestPostField: null
    }
    return (<Mutation<LatestPostMutation, LatestPostMutationVariables>
        mutation={LATEST_POST_MUTATION}
        update={(cache, result) => {
            if ((result.data as LatestPostMutation).latestPost) {
                // Update the Apollo cache
                cache.writeQuery({
                    query: LATEST_POST_QUERY,
                    data: { latestPost: (result.data as LatestPostMutation).latestPost }
                });
            }
        }}>
        {updateItem => <div>
            <input
                type="text"
                defaultValue={
                    latestPostData.latestPostField ?
                        latestPostData.latestPostField.value :
                        props.latestPost || ""
                }
                ref={node => latestPostData.latestPostField = node}
            /><br />
            <input
                type="button"
                value="Update"
                onClick={
                    () => {
                        updateItem({
                            variables: {
                                newPostVal: latestPostData.latestPostField ? latestPostData.latestPostField.value : ""
                            }
                        })
                    }
                }
            />
        </div>
        }
    </Mutation>)
}

So time to break this down as it’s pretty confusing at first.

Properties are defined as usual for a React component, then we keep a small internal variable that points to our input component. This is just so when we hit the update button, we can easily refer to it rather than having to update the value on each key press.

The Mutation wrapper component has two types defined for it. This isn’t just for mutations, it’s the same for queries that take variables. Pretty easy to figure out, the first one is the server response, and the second is the variables you pass.

Much like our query, we pass the mutation we’d like to call.

The update property is where we update the cache for viewing (this all happens automatically internally). For starters, we make sure we have data, then update the cache. The cache is based on the queries we make so we use that as our key. We also pass variables into that where appropriate (ie. when our queries are using variables), but we’re not doing that here.

Inside the wrapper component, this is where the rest of the magic happens. We get passed a function (which we’ve named updateItem) that we call to actually do the mutation request.

The rest of this is pretty generic React code setting our component ref so we can refer to it in our updateItem function.

Finally update the App.tsx again to be the following

import React, { Component } from 'react';
import './App.css';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import LatestPost from './LatestPost';
import Username from './Username';

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


class App extends Component {
  render() {
    return (
      <ApolloProvider client={client}>
        <div className="App">
          <Username />
          The latest post is: <LatestPost />
        </div>
      </ApolloProvider>
    );
  }
}

export default App;

And that’s it. Try it out. Run the server, run the client, and play. You’ll notice that if you update the item, you can refresh the page, and the value stays (ie. gets saved in the server).

If you’d like to grab the code, to play directly without copying and pasting from here, go to https://github.com/JazzXP/graphql-client-apollo


So this is the conclusion of this intro. Beyond this, the rabbit hole goes pretty deep. There’s fragments, authentication/authorisation, directives, schema design and much more to look into. Have a browse of https://graphql.org/learn/ and see what grabs your attention.

Mastodon