Learning GraphQL and React: My First
App
Tags: graphql, react, database, javascript
Published: 2020-09-28 10:15:07

I took my first leap into React a couple of months ago as part of the [freeCodeCamp](https://freecodecamp.org) web development curriculum that I was following. Since then, I've discovered a wide range of tools and technologies that power the web and software industry. So I came to know about [GraphQL](https://graphql.org), "a query language for APIs and a runtime for fulfilling those queries with your existing data". I was pretty familiar with REST, and after taking a short introduction, I realized how powerful GraphQL is; it eliminates the shortcomings of REST while providing ease of development for programmers.

I also got to read about graphs, databases and how all of these fit together into the picture. I discovered [Dgraph](https://dgraph.io), an open-source native GraphQL graph database, the only one of its kind, written entirely from scratch, in Go. So I decided to do a little project that would give me a hands-on experience with all of this. 

The idea is pretty simple:

*send queries to a GraphQL server based on user input and render the data in the UI*. 

The app is going to send queries to an existing Dgraph server instance located at https://play.dgraph.io/graphql that holds a ton of information on films via Google's [Freebase film data](https://developers.google.com/freebase/).

Let's start!

# Getting started

Getting started is totally hassle-free, thanks to [Create React App](https://github.com/facebook/create-react-app):

```shell
npx create-react-app graphql-react-app
```
This creates the app in a new directory `graphql-react.app`, and it takes only two commands to launch it in the browser:

```shell
cd graphql-react-app
npm start
```

This would start the app at http://localhost:3000/.

# Meet Apollo

[Apollo](apollographql.com/) is a GraphQL client for JavaScript. It works really well with frameworks/libraries like React, Angular etc. Now you might ask why we need a client?

Generally, all of the resources of a GraphQL service are exposed over HTTP via a single endpoint. So yes you could just use the good-old `fetch`. But it wouldn't be scalable, not unless you implement all the functionalities like caching, UI integration for React or Angular by yourself; and that's overkill. A client like Apollo comes packed with all of these functionalities, and more, so you can just focus on developing your app, without getting distracted by the extra work.

So let's install Apollo:

```shell
npm install @apollo/client graphql
```

This is going to install the following packages:

* `@apollo/client`: This is the Apollo client and with this we're set for stuffs like caching, error handling etc.
* `graphql`: This package is needed for parsing GraphQL queries.

In the `index.js` file, I import the following packages and create the client by using the `ApolloClient` constructor, while passing an object with a `uri` property whose value is the server, while also setting up the caching mechanism:

```javascript
import App from './App';
import { 
  ApolloClient, 
  ApolloProvider,
  InMemoryCache 
} from '@apollo/client';

const APOLLO_CLIENT = new ApolloClient({
  uri: "https://play.dgraph.io/graphql",
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          queryFilm: {
            merge(_ignored, incoming) {
              return incoming;
            },
          },
        },
      },
    },
  })
});

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={APOLLO_CLIENT}>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
```

The cache could be set up with just `cache: new InMemoryCache()`, but in this case, I define a custom `merge` function to silence some warnings in the console. Basically, what this does, is that this function is called by the cache every time there's an incoming value from the server which is going to overwrite the cache with the new information. The `incoming` value returned by this function is written over the `existing` data in the cache; by explicitly telling the cache to do so completely replaces the cache with new information while also silencing the warnings. This part would become clearer when I define the queries.

Now I need to connect Apollo Client with React, that is done via the `ApolloProvider` component. The app would be wrapped with this component that exposes the client to the context of the `ApolloClient` instance so that it can be used throughout the component tree, so even though we are going to do all of our work in `App.js`, the `APOLLO_CLIENT` instance is going to be available there.

I import the `App` component, and wrap it with `ApolloProvider`, passing the client instance as a prop.

# Defining our queries

Now I define the queries for the app. Here I need to use `gql` which I've already imported above. This is a utility provided by Apollo that parses GraphQL queries into what's called an "Abstract Syntax Tree (AST)". AST isn't something totally unique in GraphQL; it's a structure used by compilers such as C/C++ compilers to parse the code we humans write into "tree structures" that can be traversed. So, using `gql`, we're sending a tree-representation of our query to the server which the machine is able to understand, it then traverses the tree executing the request against the schema defined in the server.

```javascript
const QUERY_FILM_GENRES = gql`{
  queryGenre @cascade {
    name
  }
}`;

const QUERY_FIND_FILMS = gql`
  query($name: FilmFilter, $genre: GenreFilter) {
    queryFilm(filter: $name) @cascade {
      name
      genre(filter: $genre) {
        name
      }
      directed_by {
        name
      }
    }
}`;
```

There are two queries here. The first query is going to request the names of all the genres that are in the server and populate a drop-down menu. The user can select a genre, and then type in a movie name or a phrase or just a word in an input field that might belong to that particular genre; the second query is going to take all this information and make another request. The response data would hold the film name(s) and director(s) that would be shown in a table.

The second query holds two query variables: `$name` and `$genre`, that the user would supply values with, for the film name and its genre. The user might not select a genre, or no name either, in that case the value is going to be `null`.

`FilmFilter` and `GenreFilter` are both `types` that are defined in the schema of the server. In a GraphQL server, schemas define what type of information can be queried. The type system defines the types of data there are, in object-like structures. In this case, there's a `FilmFilter` object type that could hold the following fields:

```javascript
type FilmFilter {
	id
	initial_release_date
	name
}
```

Just like this, our `GenreFilter` has `id` and `name` fields. In both cases, I only care about the `name` of the genre and film so only those are in the queries.

Another thing to notice is `@cascade`. It's a directive that gives us only those genres that have `name` field inside them in the first query, and likewise films who have `name`, `genre` and `directed_by` fields in the second query. The directive flows down from where it's defined; so for the first query, each genre must have a `name`, and for the second one, each film must have a `name` and both `genre` and `directed_by` must also have a `name` inside them. If any of these fields are valued `null`, they won't be returned. `@cascade` is helpful in situations where some sort of filter is applied, in this case, I'm filtering by name and genre: `filter: $name` and `filter: $genre`.

# Components

The app is going to have three additional components besides the main `App` component. The first is going to be for the drop-down menu. The third component is simply for decoration purposes that we'll see later.

```javascript
function Genre({handleGenreSelect}) {

  let { loading, error, data } = useQuery(QUERY_FILM_GENRES);
  
  if (loading) {
      return <CircularProgress />
  } else if (error) {
      return (
        <Alert severity="error">
          <AlertTitle>Error</AlertTitle>
          Sorry, something might not be working at the moment!
        </Alert>
      )
  }

  var filmGenres = [];
  data.queryGenre.forEach(
    (genreObject) => filmGenres.push(genreObject.name));
  
  return (
    <Autocomplete 
      id="film-box" 
      options={ filmGenres } 
      onChange={ (event, selectedGenre) => handleGenreSelect(event, selectedGenre) }
      style={{ width: 300 }} 
      getOptionLabel={(option) => option}
      renderInput={
        (params) => <TextField {...params} label="Select genre" variant="outlined" />
      }>
    </Autocomplete>
  );

};
```

The `Genre` component receives a prop called `handleGenreSelect` from the `App` component; this is a handler function that's going to catch the genre value the user selects since I need it to use in the query.

This component is responsible for the drop-down menu. 

I imported `useQuery` as shown in the previous section. It's a React hook that is used to run a query in an Apollo-React app. To do this, I pass our query string, `QUERY_FILM_GENRES` to it as shown above. The hook call returns an object that contains `loading`, `error` and`data` properties. The `loading` property gives the loading state, i.e. when the data hasn't arrived yet. Any errors that might occur in the process can be caught from the `error` property. And the result of the query is contained in the `data` property after it arrives.

This might be a good place to say that I'm going to be using [Material-UI](https://material-ui.com/) as a React UI framework. Below are all its component imports in the `App.js` file:

```javascript
import Container  from "@material-ui/core/Container";
import TextField from '@material-ui/core/TextField';
import {
  Autocomplete,
  Alert,
  AlertTitle
} from '@material-ui/lab';
import Input from '@material-ui/core/Input';
import Button from '@material-ui/core/Button';
import MaterialTable from 'material-table';
import CircularProgress from '@material-ui/core/CircularProgress';
```

As long as the loading state persists, I show a progress bar to the UI using Material-UI's `CircularProgress` component. If there's an error, I show an "error" message using the `Alert` component.

If everything goes well, `data` would contain all the genre names from the server, for example:

```json
{
  "data": {
    "queryGenre": [
      {
        "name": "Crime Thriller"
      },
      {
        "name": "Educational film"
      },
      {
        "name": "Chinese Movies"
      },
      {
        "name": "Experimental film"
      }
}	
```

This is one of the neat things about GraphQL: we get exactly what we want from the server. If you compare the query and the JSON response here, you'd realize how simple it is to request something and get exactly that in return, nothing more nothing less.

I then use that array to populate the `Autocomplete` component provided by Material-UI. This component has built-in suggestion feature so when I start typing in, I get suggestions. This particular spin of `Autocomplete` is called [combo box](https://material-ui.com/components/autocomplete/#combo-box).

![Combo box for selecting genres](https://dev-to-uploads.s3.amazonaws.com/i/g5wowmonc9xgpno9gbbv.png)

The second component is for handling the user input and submit functions.

```javascript
function UserInput({handleInputChange, handleSubmit}) {

  return (
    <form>
      <Input placeholder="Film name" onChange={ handleInputChange }>
      </Input>
      <Button type="submit" variant="contained" onClick={ handleSubmit } color="primary" style={{ marginLeft: 20 }}>
        Submit
      </Button>
    </form>
  );

};
```

It takes two props from the `App` component, both are handler functions just like the previous one: `handleInputChange` catches what the user types in in the input field of the`Input` component, while `handleSubmit` is triggered as soon as the "submit" `Button` is pressed. The query is then sent to the server to get the desired data.

![Rendering of the UserInput component](https://dev-to-uploads.s3.amazonaws.com/i/5pvq5wwj6nf23sviayq7.png)

And now inside the `App` component, I define the necessary states using `useState` hook:

```javascript
const [ nameFilter, setNameFilter ] = useState({name: {alloftext: "Summer"}});
const [ genreFilter, setGenreFilter ] = useState(null);
const [ dataForRender, setDataForRender ] = useState([]);
```

Remember when I defined the second query and there were two filters applied to `$name` and `$genre`?

```javascript
queryFilm(filter: $name)
genre(filter: $genre)
```

Since the user would type in a phrase or word to search for a film, I have to take that into consideration and hence I use a filter. So for example, if the user types in the word "Summer" and selects nothing as the genre, it would look like this:

```javascript
"name": {"name": {"alloftext": "Summer"}},
"genre": null
```

So `"name": {"name": {"alloftext": "Summer"}}	` and `null` would be the values for our two variables `$name` and `$genre` respectively.

What if the user selects a genre from the drop-down menu, say for example, "Animation"? Then we would have:

```javascript
"genre": {"name":{"eq": "Animation"}}
```

Notice that they are very much the same.

You can have a clearer vision of this if you use a GraphQL IDE like [GraphQL Playground](https://github.com/prisma-labs/graphql-playground) or [GraphiQL](https://github.com/graphql/graphiql) and use the query in the query field and supply the relevant variables. See below for a snapshot:

![A screenshot of GraphQL Playground](https://dev-to-uploads.s3.amazonaws.com/i/ggy3lm64bjxr29vz6mug.png)

Keeping these in mind, I define the first state containing value for `$name` variable as `{name: {alloftext: "Summer"}}` (notice that using quotation around `name` and is not necessary here). `$genre` is set to `null`.

The third hook is for the final data that I need to show; using `setRenderData` would cause that component to re-render as soon as the data arrives and is ready to be shown to the user.

Using `useQuery`, I run the second query:

```javascript
const { loading, error, data, refetch } = useQuery(QUERY_FIND_FILMS, 
    { variables: {name: nameFilter, genre: genreFilter} });
```

This time I'm also passing the variables as a second argument, which is an object, to the hook call.

Now let's look at the handler functions defined in the `App` component that are passed as props to other components as we saw previously.

## Handlers

I need three handlers for my app: for catching what genre the user selects, what the user types in the input field and the click on the submit button:

```javascript
const handleGenreSelect = (event, selectedGenre) => {
    if(selectedGenre) {
      setGenreFilter({name: { eq: selectedGenre }});
    } else {
      setGenreFilter(null);
    }
};

const handleInputChange = (event) => {
    if (event.target.value) {
      setNameFilter({name: {alloftext: event.target.value}});
    } else {
      setNameFilter(null);
    }
};
```

Here are handlers for the first two cases. 

`handleGenreSelect` is used by `AutoComplete`as we saw previously:

```javascript
onChange={ (event, selectedGenre) => handleGenreSelect(event, selectedGenre) }
```

So for an `onChange` event on the `AutoComplete` component, I define a function that calls `handleGenreSelect` with that specific event and `selectedGenre` as the value of what the user selected. If the user doesn't select anything, `selectedGenre` would be null, so I set the state accordingly; if the user selects a genre, I set the state equal to that value using `setGenreFilter`.

`handleInputChange` is for the input field to catch whatever the user typed through `event.target.value` and set the state using `setNameFilter`. Just like `handleGenreSelect`, here I also check for `null`.

Before looking at the third handler, let's define a couple of variables:

```javascript
var filmsAndDirectors;
var arrayOfFilmNames = [];
var arrayOfFilmDirectors = [];
var multipleDirectors = "";
```

Now here's our final and most important handler:

```javascript
const handleSubmit = async (event) => {
  event.preventDefault();
  const { data: newData } = await refetch({ 
    variables: {name: nameFilter, genre: genreFilter} 
  });

  // get film names
  newData.queryFilm.forEach((filmObject) => arrayOfFilmNames.push(filmObject.name));

  // get corresponding directors
  newData.queryFilm.forEach((filmObject) => {
    // for multiple directors show in comma-separated list
    if (filmObject.directed_by.length > 1) {
      filmObject.directed_by.forEach((dirObject) => {
        multipleDirectors += dirObject.name + ", ";
      })
      arrayOfFilmDirectors.push(
        multipleDirectors.trim().substr(0, multipleDirectors.length - 2));
      multipleDirectors = "";
    } else {
      filmObject.directed_by.forEach((dirObject) => arrayOfFilmDirectors.push(dirObject.name))
    }
  });

  // create array of objects of film and their directors
  filmsAndDirectors = [];
  var tempObj = {};
  arrayOfFilmNames.forEach((key, i) => {
    tempObj.name = key;
    tempObj.director = arrayOfFilmDirectors[i];
    filmsAndDirectors.push(tempObj);
    tempObj = {};
  });
  setDataForRender(filmsAndDirectors);
};
```

As soon as the "submit" button is clicked, this handler is triggered. Inside, I call another function called `refetch`, that was extracted earlier as part of the `useQuery` call. Refetching is required in these types of situations when we need to "update" our query results based on user actions.

`refetch` returns a `Promise`, which when resolved successfully, would indicate that the desired data has arrived. That's why I use an `async` function here and an `await` inside it to wait for `refetch` to finish its task. The `refetch` function takes the variables as parameters that contain all the user input: genre and the film name/phrase/word.

After the promise resolves successfully, the data is contained in `newData`. For example, if the user selected "Animation" as genre and typed in "Fantastic", the response gives all films in that genre that contains that word and their directors:

```json
  "data": {
    "queryFilm": [
      {
        "name": "Fantastic Planet",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "René Laloux"
          }
        ],
        "initial_release_date": "1973-05-01T00:00:00Z"
      },
      {
        "name": "The Cameraman's Revenge & Other Fantastic Tales",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "Ladislas Starewitch"
          }
        ],
        "initial_release_date": "1958-01-01T00:00:00Z"
      },
      {
        "name": "Noel's Fantastic Trip",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "Tsuneo Maeda"
          }
        ],
        "initial_release_date": "1983-04-29T00:00:00Z"
      },
      {
        "name": "Fantastic Mr. Fox",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "Wes Anderson"
          }
        ],
        "initial_release_date": "2009-10-14T00:00:00Z"
      },
      {
        "name": "Fantastic Animation Festival",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "Christopher Padilla"
          },
          {
            "name": "Dean A. Berko"
          }
        ],
        "initial_release_date": "1977-05-27T00:00:00Z"
      },
      {
        "name": "The Fantastic Flying Books of Mr. Morris Lessmore",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "William Joyce"
          },
          {
            "name": "Brandon Oldenburg"
          }
        ],
        "initial_release_date": "2011-01-30T00:00:00Z"
      },
      {
        "name": "Daffy Duck's Fantastic Island",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "Friz Freleng"
          },
          {
            "name": "Chuck Jones"
          },
          {
            "name": "Phil Monroe"
          }
        ],
        "initial_release_date": "1983-01-01T00:00:00Z"
      },
      {
        "name": "Piper Penguin and His Fantastic Flying Machines",
        "genre": [
          {
            "name": "Animation"
          }
        ],
        "directed_by": [
          {
            "name": "Michael Schelp"
          }
        ],
        "initial_release_date": "2008-01-01T00:00:00Z"
      }
    ]
  },
  "extensions": {
    "touched_uids": 470
  }
}
```

From this data, I extract all the film names and their corresponding directors in two arrays called `arrayOfFilmNames` and `arrayOfFilmDirectors`. Then an array of objects is constructed that would hold all this information in `filmsAndDirectors`. In this case, `filmsAndDirectors` would be:

```javascript
[
  { name: 'Fantastic Planet', director: 'René Laloux' },
  {
    name: "The Cameraman's Revenge & Other Fantastic Tales",
    director: 'Ladislas Starewitch'
  },
  { name: "Noel's Fantastic Trip", director: 'Tsuneo Maeda' },
  { name: 'Fantastic Mr. Fox', director: 'Wes Anderson' },
  {
    name: 'Fantastic Animation Festival',
    director: 'Christopher Padilla, Dean A. Berko,'
  },
  {
    name: 'The Fantastic Flying Books of Mr. Morris Lessmore',
    director: 'William Joyce, Brandon Oldenburg,'
  },
  {
    name: "Daffy Duck's Fantastic Island",
    director: 'Friz Freleng, Chuck Jones, Phil Monroe,'
  },
  {
    name: 'Piper Penguin and His Fantastic Flying Machines',
    director: 'Michael Schelp'
  }
]
```

Using `setRenderData`, which is initially assigned an empty array, I set the state and assign it the value of `filmsAndDirectors`. So if all goes well, hitting the submit button would set the state with new information and the component would re-render.

You might've noticed the `extensions` field in the response; it contains some metadata for the request; in this case `touched_uids` indicate how many nodes were touched to get the data (remember AST?).

## Final Result

Let's look at the `App` component's `return` function where I return all the components defined so far:

```javascript
return (

  <div>
    <Header />
    <br></br>
    <Container maxWidth="xs" style={ getContainerStyle }>

      <Genre handleGenreSelect={handleGenreSelect} />
      <br></br>

      <h3 style={{ marginTop: 50 }}>
        Enter a film name or phrase:
      </h3>

      <UserInput handleInputChange={handleInputChange} handleSubmit={handleSubmit} />

    </Container>
    <MaterialTable 
        title=""
        columns={[
          { title: 'Name', field: 'name', align: 'center', headerStyle: {
            backgroundColor: '#A5B2FC'
          } },
          { title: 'Director', field: 'director', align: 'center', headerStyle: {
            backgroundColor: '#A5B2FC'
          } }
        ]}
        data={
          dataForRender
        }
        options={{
          search: true
        }}
        style={{ margin: '5rem' }}>
    </MaterialTable>
  </div>
);
```

`Header` is simply a header bar using Material-UI's `Appbar` as follows:

```javascript
import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';

function Header() {
    return (
        <AppBar position="static">
          <Toolbar>
            <h2>Film Information</h2>
          </Toolbar>
        </AppBar>
    )
};

export default Header;
```

`Container` is Material-UI's layout component that centers all its children horizontally. The `maxWidth` property tells it to grow according to the size of the screen; here I assign it the value of `xs` which means "extra small" screens. The container is styled using the `getContainerStyle` object:

```javascript
const getContainerStyle = {
  marginTop: '5rem'
};
```

Then I place the `Genre` and `UserInput`component, passing relevant handlers as props.

Next is `MaterialTable`, the table where the film names and corresponding directors would be shown. The reason I created an object of films and their directors is because this component takes in an array of objects as its `data` property, that is going to be shown in table columns and rows.

Details about the columns are passed into the `columns`property as an array of objects.

I create two columns, one for the film names, with the title `Name`, and the other for their directors, with the title `Director`. The `field` property corresponds to the key names in the array of objects that was created, `filmsAndDirectors`, which was used to set the state data; they must be the same. 

Columns are centered using the `align` property, as well as custom styling for the column header by assigning an object to the `headerStyle` property.

`data` takes in the array of objects from the state, `dataForRender`, which is equal to `filmsAndDirectors`. Through the `options` property, I set `search` option as true so that the user can search among the table data. Then, some custom styling is applied using the `style` property.

Lastly, I export `App` to be used in `index.js`:

```javascript
export default App;
```
Let's start the app with `npm start` from the root of the app directory, select a genre "Animation", type in "fantastic" as our search term, and hit the submit button:

![A GIF of the application](https://dev-to-uploads.s3.amazonaws.com/i/8sbdth9gf39c3n554hhh.gif)

It works!

This simple app shows the core tools to work with for a scenario such as this where we need to build a web app that communicates with a GraphQL server. Using a client like Apollo and having basic understanding of GraphQL, a lot of work becomes easier. I learned a lot about graphs, GraphQL, GraphQL servers, React and much more.

Hopefully, as I attempt to build more stuff, I'd gain a tighter grasp of React and GraphQL.