Getting Started with GraphQL and React Native

In 2012, Facebook engineer Nick Schrock started work on a small prototype to facilitate moving away from an old, unsupported partner API that powered the current Facebook News Feed. At the time, this was called “SuperGraph”. Fast forward to today and SuperGraph has helped shape the open-source query language GraphQL, which has been much of the buzzword in recent times.

Facebook describes GraphQL as a “query language for APIs and a runtime for fulfilling those queries with your existing data”. Put simply, GraphQL is an alternative to REST that has been steadily gaining popularity since its release. Whereas with REST a developer would usually collate data from a series of endpoint requests, GraphQL allows the developer to send a single query to the server that describes the exact data requirement.

Want to learn React Native from the ground up? This article is an extract from our Premium library. Get an entire collection of React Native books covering fundamentals, projects, tips and tools & more with SitePoint Premium. Join now for just $9/month.

Prerequisites

For this tutorial, you’ll need a basic knowledge of React Native and some familiarity with the Expo environment. You’ll also need the Expo client installed on your mobile device or a compatible simulator installed on your computer. Instructions on how to do this can be found here.

Project Overview

In this tutorial, we’re going to demostrate the power of GraphQL in a React Native setting by creating a simple coffee bean comparison app. So that you can focus on all of the great things GraphQL has to offer, I’ve put together the base template for the application using Expo.

A mockup of our coffee comparison app

To get started, you can clone this repo and navigate to the “getting-started” branch, which includes all of our basic views to start adding our GraphQL data to, as well as all of our initial dependencies, which at this stage are:

{
    "expo": "^32.0.0",
    "react": "16.5.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
    "react-navigation": "^3.6.1"
}

To clone this branch, you’ll need to open up terminal and run this command:

git clone https://github.com/jamiemaison/graphql-coffee-comparison.git

To then navigate to the getting-started branch, you move into the newly cloned repo with cd graphql-coffee-comparison and run git checkout getting-started.

The next stage is to install our dependencies. To do this, make sure you’re on Node v11.10.1 and run npm install in the root directory of the project. This will add all of the dependencies listed above to your node_modules folder.

To start adding GraphQL to our React Native app, we’re going to need to install a few more dependencies that help us perform a few simple GraphQL functions. As is common with modern JavaScript development, you don’t need all of these dependencies to complete the data request, but they certainly help in giving the developer a better chance of structuring some clean, easy-to-read code. The dependencies you’ll need can be installed by running npm install --save apollo-boost react-apollo graphql-tag graphql.

Here’s an overview of what these dependencies are:

  • apollo-boost: a zero-configuration way of getting started with GraphQL in React/React Native
  • react-apollo: this provides an integration between GraphQL and the Apollo client
  • graphql-tag: a template literal tag that parses GraphQL queries
  • graphql: the JavaScript reference implementation for GraphQL

Once all of the necessary dependencies have finished installing, run npm start. You should now see your familiar Expo window, and if you launch the app (either via a simulator or on a device) then you should see a screen similar to this:

A mockup of our getting started page

In basic terms, this application has two screens that are managed by react-navigation, Home.js and CoffeePage.js. The Home screen contains a simple FlatList that renders all of the coffee beans supplied to its data field. When clicked on, the user is navigated to the CoffeePage for that item, which displays more information about the product. It’s our job to now populate these views with interesting data from GraphQL.

The complete coffee page

Apollo Server Playground

There are two main elements to any successful GraphQL transaction: the server holding the data, and the front-end query making the request. For the purposes of this tutorial, we aren’t going to start delving into the wonderful world of server-side code, so I’ve created our server for us ready to go. All you need to do is navigate to yq42lj36m9.sse.codesandbox.io in your favorite browser and leave it running throughout the course of development. For those interested, the server itself is running using apollo-server and contains just enough code to hold the data we need and serve it upon receiving an appropriate query. For further reading, you can head over to apollographql.com to read more about apollo-server.

GraphQL Query Basics

Before we get into writing the actual code that’s going to request the data we need for our coffee bean comparison app, we should understand just how GraphQL queries work. If you already know how queries work or just want to get started with coding, you can skip ahead to the next section.

Note: these queries won’t work with our codesandbox server, but feel free to create your own at codesandbox.io if you’d like to test out the queries.

At its simplest level, we can use a flat structure for our queries when we know the shape of the data we’re requesting:

QUERY:                                RESPONSE:
{                                     {
  coffee {                              "coffee": {
    blend                                 "blend": "rich"
  }                                     }
}                                     }

On the left, we see the GraphQL query requesting the blend field from coffee. This works well when we know exactly what our data structure is, but what about when things are less transparent? In this example, blend returns us a string, but queries can be used to request objects as well:

QUERY:                                RESPONSE:
{                                     {
  coffee {                              "coffee": {
    beans {                               "beans": [
        blend                               {
    }                                         blend: "rich"
  }                                         },
}                                           {
                                              blend: "smooth"
                                            }
                                          ]
                                        }
                                      }

Here you can see we are simply requesting the beans object, with only the field blend being returned from that object. Each object in the beans array may very well contain other data other than blend, but GraphQL queries help us request only the data we need, cutting out any extra information that’s not necessary for our application.

So what about when we need to be more specific than this? GraphQL provides the capability for many things, but something that allows for extremely powerful data requests is the ability to pass arguments in your query. Take the following example:

QUERY:                                RESPONSE:
{                                     {
  coffee(companyId: "2") {              "coffee": {
    beans {                               "beans": [
        blend                               {
    }                                         blend: "rich"
  }                                         },
}                                           {
                                              blend: "smooth"
                                            }
                                          ]
                                        }
                                      }

What we see is that we can pass an argument — in this case, the companyId — which ensures that we are only returned beans from one particular company. With REST, you can pass a single set of arguments via query params and URL segments, but with GraphQL querying every single field, it can get its own set of arguments. This allows GraphQL to be a dynamic solution for making multiple API fetches per request.

Aliases

So far, all of our queries have had the fields of the returned object match the name of the field in the query itself. This is great when using simple arguments, but what if you want to query the same field with different arguments in your data request? This is where aliases come in. Aliases let you change the name of a field so you can rename any of the returned data and therefore use different arguments in your data request. Take our coffee bean example. What if we wanted to return data from two different company IDs? We would structure our query like this:

QUERY:                                    RESPONSE:
{                                         {
  company1: coffee(companyId: "1") {        "company1": {
    beans {                                   "beans": [
      blend                                     {
    }                                             "blend": "rich"
  }                                             }
  company2: coffee(companyId: "2") {          ]
    beans {                                 },
      blend                                 "company2": {
    }                                         "beans": [
  }                                             {
}                                                 "blend": "fruity"
                                                }
                                              ]
                                            }
                                          }

Here, we request data for aliases company1 and company2, which are simply different coffee queries stacked on top of each other. Aliases can be a powerful tool in modifying your requires for your exact data requirement.

Variables

Up until now, we’ve known our exact query and can therefore hardcode it in our application, but most applications will need these fields to be dynamic. For instance, the user might select a coffee bean company from a list to display. We won’t know ahead of time which coffee bean company the user is selecting, so we need a way of passing in these requirements. This is where variables come in.

The GraphQL documentation lists three things that we need to do in order to use variables:

  • replace the static value in the query with $variableName
  • declare $variableName as one of the variables accepted by the query
  • pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary

In practical terms, this means our data query will look something like this:

query coffeeCompany(companyId: CompanyId) {
    coffee(companyId: companyId) {
        beans: {
            type
        }
    }
}

We would also pass in companyId as a JSON object:

{
    "companyId": "1"
}

Using variables within GraphQL is a powerful way of making all of our query requests dynamic, as we’re requesting only the data we currently need.

Coffee Query

For the purposes of our application, we’re going to need a query that allows us to request data retrieving only our coffee beans, whilst including all of the relevant fields we’re going to need within that. Our data requirements aren’t that complex, so it will look something like this:

{
    coffee {
        beans {
            key
            name
            price
            blend
            color
            productImage
        }
    }
}

Requesting our Data

Now for the actual code that we’re going to use to request our data. Open up App.js, which is the container for all of our views and will be a good place to make our data request when the app launches.

We’re going to want to initialize our client, which we can simply do by importing ApolloClient from apollo-boost and specifying our server URL. It’s important to note that you need to have the server initialized, which is achieved by simply running yq42lj36m9.sse.codesandbox.io in your browser. Occasionally you might find the server timing out. If Expo returns a warning that looks something like “network error”, reload yq42lj36m9.sse.codesandbox.io on your browser to re-initialize the server.

Once the server is running, add the imports and initialization to the top of App.js, which should look something like this:

// ./App.js
import ApolloClient from 'apollo-boost';

const client = new ApolloClient({ uri: 'https://yq42lj36m9.sse.codesandbox.io/' })

Next, we want to assemble the graphQL query for later use when we request our data. Luckily, the graphql-tag library makes this simple. Again, we need to import the library itself to App.js:

// ./App.js
import gql from 'graphql-tag';

Now we can structure the query:

// ./App.js
const QUERY = gql`
{
    coffee {
        beans {
            key
            name
            price
            blend
            color
            productImage
        }
    }
}
`

The next step is to amend the render function to include our data request. To do this, we use the react-apollo library to make the request, which allows us to handle the response as we see fit. Add a new import to App.js:

// ./App.js
import { ApolloProvider, Query } from 'react-apollo';

Then change the render function so that it now looks like this:

// ./App.js
render() {
    return (
    <ApolloProvider client={client}>
        <Query query={QUERY} >
        {({ loading, error, data }) => {
            if (loading || error) return <View />
            return <View style={{ flex: 1 }}>
            {this.state.isFontsLoaded ? <AppContainer /> : <View />}
            </View>
        }}
        </Query>
    </ApolloProvider>
    );
}

Here, you can see we’re using the QUERY we created earlier to request the necessary data. At this moment in time, we’re simply rendering an empty view whilst loading, and if there’s an error in the data request. In practice, this would be swapped out for the relevant loading and error views, but for this example we’ll leave them blank. Once data is returned, we’re rendering our AppContainer as usual. You can check that data is coming through by checking that data is being returned successfully. This can be checked by adding a console.log(data) to your code to view the output in your terminal. You should be receiving an object with our coffee and beans fields as long as your Apollo server is running without a problem.

Storing Data with the Context API

We’re going to need somewhere to store our data that will be accessible in any of our components no matter how far down the tree they are. If we were to pass the data all the way through several children just to get to our component, that wouldn’t be the most efficient thing. Given that our data storage needs are fairly simple for this example, it would be good to use React’s Context API rather than some more complex state management tool like Redux. The Context API lets you pass global state down our component tree without the need for passing it through props each time, and for our current example, this is enough.

The benefits of Redux on top of the Context API can broadly be narrowed down to three points:

  • Redux comes with a time traveling debugger
  • it provides the developer with middleware APIs, giving you access to tools like redux-sagas
  • its React bindings prevent having too many renders

Using the Context API couldn’t be simpler. In essence, we just need to create a <Provider /> component to store all of our data in and access the data by creating a <Consumer /> component when we come to need it.

Creating a Provider

Let’s go back to App.js, where we only have to add a couple of lines to get our Provider up and running. First, we’ll start off by creating our AppContext. We’ll need to access this in any file where we want to use the stored data, so we’ll need to make sure it’s exported. To create the AppContext, add the following line to App.js:

// ./App.js
export const AppContext = React.createContext({ data: { coffee: { beans: [] } } });

Here, we’re creating the context and initializing it with some blank data. Next, we want to populate the AppProvider with the data we’re receiving from the GraphQL server.

Storing cCoffee Data

To update our provider with the data, we simply have to change out the blank container view in our App.js render function for the provider whilst adding our GraphQL data to its data prop. This looks like this:

// ./App.js
render() {
    return (
        <ApolloProvider client={client}>
        <Query query={QUERY} >
            {({ loading, error, data }) => {
            if (loading || error) return <View />
            return <AppContext.Provider value={data.coffee.beans} style={{ flex: 1 }}>
                {this.state.isFontsLoaded ? <AppContainer /> : <View />}
            </AppContext.Provider>
            }}
        </Query>
        </ApolloProvider>
    );
}

Here, you can see that we’re directly storing the bean data (data.coffee.beans) in our provider. At this point we have all of the data necessary, but we’re still rending our placeholder content. The final piece of this puzzle is to amend Home.js to render our data by using a Consumer.

Creating an App Consumer

Firstly, we need to import our AppContext from earlier to make use of the Consumer. To do this, we simply need to import it from App.js into Home.js:

// ./src/Home.js
import { AppContext } from '../App';

Using a Consumer works like any other React component. For our current purposes, we’ll add it to our render function and use the data to populate our FlatList. Our render function should look something like this:

// ./src/Home.js
render() {
    return (
        <AppContext.Consumer>
        {
            (context) =>
            <View style={styles.container}>
                <Image style={styles.header} source={require('../assets/header.png')} />
                <View style={{ marginLeft: 30, marginTop: 30 }}>
                <Text style={styles.title}>COFFEE</Text>
                <Text style={styles.subtitle}>SELECTION</Text>
                </View>
                <FlatList
                style={styles.list}
                data={context}
                renderItem={({ item }) => <TouchableOpacity style={styles.block} onPress={() => this.props.navigation.navigate('CoffeePage', { item: item })}>
                    <Image style={styles.productImage} source={{ uri: item.productImage }} />
                    <Text style={styles.name}>{item.name}</Text>
                    <Text style={styles.price}>{item.price}</Text>
                </TouchableOpacity>}
                numColumns={2}
                />
            </View>
        }
        </AppContext.Consumer>
    );
}

If we go through the above code, you can see the AppContext.Consumer provides us with a context, which contains our GraphQL data. We use this context to populate the FlatList component by passing it to the data prop. When the user clicks on one of the coffee items, our data is passed through navigation params to our CoffeePage.js, allowing it to be accessed in that view. If you now save your modified files and launch the App on Expo, you should see your fully populated FlatList.

The finished App

Summary

Congratulations! You’ve successfully used GraphQL to retrieve data and render that data using React Native. We’ve learned how powerful GraphQL queries can be, whilst highlighting the benefits over a system like REST. I encourage you to use GraphQL in your next project and judge for yourself how much quicker it can be for retrieving data — particularly in data-rich applications.

If you want to explore GraphQL in further detail, I recommend you read the “Queries and Mutations” section of the GraphQL docs, and perhaps start coding an Apollo Server yourself using codesandbox.io.

The entire code for this project can be found on GitHub, so feel free to clone/fork the repo and make your own improvements!

Replies

  1. Hi,
    I have a problem with the project. The first, I’m new on React Native and GraphQL and maybe I’m doing something wrong.

    I’m trying to run the project on windows 10

    After, I run npm start I have this issue

    nt\javascript\graphql-coffee-comparison\node_modules\metro-config\src\defaults\blacklist.js:38
      return new RegExp(
             ^
    
    SyntaxError: Invalid regular expression: /(.*\\__fixtures__\\.*|node_modules[\\\]react[\\\]dist[\\\].*|website\\node_modules\\.*|heapCapture\\bundle\.js|.*\\__tests__\\.*)$/: Unterminated character class
        at new RegExp (<anonymous>)
        at blacklist (c:\Data\Development\javascript\graphql-coffee-comparison\node_modules\metro-config\src\defaults\blacklist.js:38:10)
        at getBlacklistRE (c:\Data\Development\javascript\graphql-coffee-comparison\node_modules\react-native\local-cli\util\Config.js:56:8)
        at Object.<anonymous> (c:\Data\Development\javascript\graphql-coffee-comparison\node_modules\react-native\local-cli\util\Config.js:71:13)
        at Module._compile (internal/modules/cjs/loader.js:945:30)
        at Module._compile (c:\Data\Development\javascript\graphql-coffee-comparison\node_modules\pirates\lib\index.js:99:24)
        at Module._extensions..js (internal/modules/cjs/loader.js:962:10)
        at Object.newLoader [as .js] (c:\Data\Development\javascript\graphql-coffee-comparison\node_modules\pirates\lib\index.js:104:7)
        at Module.load (internal/modules/cjs/loader.js:798:32)
        at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    
    Metro Bundler process exited with code 1
    Set EXPO_DEBUG=true in your env to view the stack trace.
    npm ERR! code ELIFECYCLE
    npm ERR! errno 1
    npm ERR! @ start: `expo start`
    npm ERR! Exit status 1
    npm ERR!
    npm ERR! Failed at the @ start script.
    npm ERR! This is probably not a problem with npm. There is likely additional logging output above.