Learning GraphQL and React: Using custom queries and remote types
Tags: graphql, react, database, javascript
Published: 2020-11-05 11:51:52

In my [previous article](https://dev.to/selectiveduplicate/learning-graphql-and-react-my-first-app-438l) I tried to create an application that interacts with an existing GraphQL API to fetch some film data based on user query.

Let's see if I can extend the app's functionality a little so that it does some more stuff for us, by being more than just a bare bones "search and see" tool.

What I'd like to add is a system that would enable users to write reviews for movies and rate them. That means I'd need a backend to save those user data. That can easily be done with [Slash GraphQL](https://dgraph.io/slash-graphql) which gives us a backend with a `/graphql` endpoint. I'll show how one can be deployed shortly. There's a free tier available so you can just go [here](https://slash.dgraph.io/), sign up, upload a schema that accurately represents your API and you'd have backend ready to be used.

So here's how the app should behave after I'm done with the new implementations:

* Just like before, we can search by a word or term for a movie, the results would appear in a table with all the films and their directors
* Clicking on a movie that we'd like write a review about would take us to someplace where we can type in a username, give that movie a rating, write our review and hit that satisfying submit button...
* Our submission details would get added to the database. There would be a separate page/route where we can see all the review details.

Alright then, let's start writing some code!

## The Schema

It's obvious that I need to add (and store) some information to a database considering the second point above. In GraphQL terms, that's called [mutation](https://graphql.org/learn/queries/#mutations). A mutation would be run each time a user hits that submit button and the details would get written to our database.

Secondly, since I'm hoping that the app would nicely show all the information that are currently in the database, I need to "fetch" (no not the `fetch` API!) them. You can smell it right? Yeah, I'm talking about sending "queries" to our database...

So I need a [schema](https://graphql.org/learn/schema/) to define exactly what "types" of information would constitute my backend. One of the coolest things about Slash GraphQL is that all I need to do in order to have a working API, is to do just that: creating a schema. The rest is taken care of automatically; I'd have a fully-working GraphQL service that can accept queries, mutations and all that stuff.

Here's the schema:

```graphql
type User {
    username: String! @id
    posted_reviews: [Review] @hasInverse(field: posted_by)
}

type Review {
    id: ID!
    text: String!
    rating: Int!
    posted_by: User!
    reviewed_film: FilmData @hasInverse(field: reviews)
}

type Film @remote {
    id: ID!
    name: String!
    directed_by: [Director!]!
}

type FilmData {
    id: String! @id
    reviews: [Review]
    data: Film @custom(
        http: {
            url: "https://play.dgraph.io/graphql"
            method: "POST"
            forwardHeaders: ["Content-Type"]
            graphql: "query($id: ID!) { getFilm(id: $id) }"
            skipIntrospection: true
        }
    )
}

type Director @remote {
    name: String!
    id: ID!
}

type Query {
    getMovieNames(name: String): [Film] @custom(
        http: {
            url: "https://play.dgraph.io/graphql"
            method: "POST"
            forwardHeaders: ["Content-Type"]
            graphql: "query($name: String!) { queryFilm(filter: {name: {alloftext: $name}}) }"
            skipIntrospection: true
        }
    )
}	
```

Let's break it down by each type:

### `User` type

The `User` type is for us users. The fields inside the user type (or object) defines the properties/attributes of that object. In this case, each user would have a `username` and some reviews that he/she's written about films.

The `username` is a `String` type which is a built-in [scalar type](https://graphql.org/learn/schema/#scalar-types) of the GraphQL query language; beside `String` you have `Int` for integers, `float` for floating-point values and so on. It's obvious that they're pretty much the same thing as the primitive data types various programming language offer. Each type ultimately represents actual valid data so that makes sense. 

The exclamation mark indicates that the field is *non-nullable*, which means that the API would always give a value when I query for a user's `username`.

`@id` is called a [directive](https://graphql.org/learn/queries/#directives) that says that each username is going to be unique and hence will be used as an ID of that user.

The `posted_reivews` field is an array of `Review` types (which I'll discuss next): this field signifies the fact that a user has written some reviews that is accessible by querying for this field.

`@hasInverse` is another directive establishes connection between a review and the `posted_by` field of the `Review` type, in both directions. What this means is that I'm associating a review with the the user who wrote it. Since it establishes a bi-directional edge between two nodes, I can also get from a review to the person who wrote it. This is neat; remember that a GraphQL API can give you quite the flexibility on how you set up your data and able to interact with them. This directive is a neat proof of that.

It isn't a native GraphQL thing though, but rather provided by Dgraph. You can look at the other directives that Dgraph supports [here](https://dgraph.io/docs/graphql/directives/).

### `Review` type

This type represents a user's reviews. So what fields does it contain?

* The `id` field that just attaches a unique identifier (the `ID` is another default scalar type of GraphQL) to each review
* The `text` field is the textual content of the review, which is of course a `String`
* `Rating` represents the rating given to a film by a user (my app would employ a 5-star rating system), which would be an integer
* `posted_by` field, as I told before, is for associating a review with a user. We're representing users under the `User` type right? So that's the value of this field
* Lastly, `reviewed_film` represents which film the review is about. I'm associating it with the `reviews` field of the `FilmData` type. This would become clearer when I talk about that field, but basically doing this would enable me to get info about the reviewed film, like its name and director.

Now the the juicy stuff begins. Notice that I need to work with two kinds of dataset here corresponding to two GraphQL APIs: one that is "remote", i.e. the information that I'd get from the remote server (https://play.dgraph.io/graphql), and the other that's going to reside in the app's own database. My app is using remote data for processing. We need to establish connection between that and what the users would supply (the usernames, ratings and reviews), since after processing I'm storing the final result in our backend by running mutations; I'd also need the ability to run useful queries. So I'm talking about a kind of "combination" of data, part of which comes from "outside" the app, part of which is the result of user interaction with that outside data.

Let's discuss about the next types and discuss how they're going to play the key role in this scenario

### `Film` type

This is a [remote type](https://dgraph.io/docs/graphql/custom/directive/#remote-types), indicated by the `@remote` directive, meaning that this field represents data that comes from somewhere else, not the native API this schema is belongs to. You guessed it right, this type is for holding the data fetched from the remote Dgraph server. We have to write our own resolver for this type, since it's a remote one.

The fields are pretty obvious; `name` is for the film name, and `id` is an associated unique ID. Notice the field `directed_by` has the value `[Director!]!`. The outer exclamation mark means the same thing: the field is non-nullable, i.e. I can always expect an array of `Director` objects, with zero or more items. The `Director!` being also non-nullable, ensures that each item of this array is going to be a `Director` object. It being a remote type, `Director` is also going to be the of the same type.

### `FilmData` type

This is the type inside whicn I'm going to be establishing a connection between our local data and the remote one. Notice that this doesn't have any `@remote` attached, so this would get stored in our Dgraph backend.

First I have the `id` field which is a `String` and also works as a unique identifier.

Then there's the `reviews` field that we saw in the previously discussed `Review` type where I established a two-way edge between this and the `reviewed_film` node. This would enable me to do a query like the following:

```graphql
queryReview {
    reviewed_film {
      id
      data {
        name
      }
      reviews {
        posted_by {
          username
        }
        id
        rating
        text
      }
    }
}
```

So I'd be able to get all reviews of each film in our database.
In fact, this would be the exact query that I use later to implement a route where the app would show all the reviews arranged by films.

Since a film might have multiple reviews by multiple users, here I've defined an array of `Review` objects as the value.

The `data` field is the "custom" field, where we write our resolver for the remote `Film` type, making a connection between the remote data and local. The syntax is quite understandable; an http POST request would send a `graphql` call	 to the the remote `https://play.dgraph.io/graphql` by `id` (which I'm going to supply from within the app based on what film the user selected, as we'll see soon). The result would be a JSON response object with data matching the fields of the `Film` type. As you can see from the above query structure, I can access that through this custom `data` field. Hence I've effectively established my desired connection; basically I now have a node that holds a copy of my remote data so I can traverse it for meaningful queries.

### `Director` type

This, as I mentioned, is also a remote type and part of `Film` that represents the director's name and ID.

### `Query` type

This is the type responsible for managing the search functionality of the app. Let's go over that again a bit more:

* We would type in a word or term, which is just a `String`, and a query should be fired towards the remote server, fetching all the film's whose names contain our search term.
* The response would consist of the film names and their directors' names. I also need to get the IDs of those films since I need that for the custom `data` field of `FilmData`.

I give the query a name, `getMovieNames` (this is the name I'd use inside our app to fire the query, with variables that would hold the user's search term, just like we saw in the first version of the app), which has an argument called `name`, which is a `String`, corresponding to the search term	. We've already seen the remote `Film` type that contains fields that would suit our needs for the response we're hoping to get. So that's what I use here; we might get multiple results, which means I have to use an array of `Film` objects, and hence I use `[Film]`. In the `graphql` field of the HTTP request object, I pass in the search term using the variable `name` and define the custom query.


## Deploying a backend

With the schema ready, it just needs to be uploaded to Slash GraphQL to get a production-ready service up and running. 

First we need to head over to https://slash.dgraph.io. There'll be a a log in/sign up page.

![Slash GraphQL signup/signin page](https://dev-to-uploads.s3.amazonaws.com/i/fyr35ianijw2zqx2l9ka.png)

After registering, we're presented with the following:

![Slash GraphQL after signup](https://dev-to-uploads.s3.amazonaws.com/i/h6a5v74z6bmko0bbh3ds.png)

Just click on the **Launch a New Backend** button.

![Create a new Slash GraphQL backend](https://dev-to-uploads.s3.amazonaws.com/i/2k6qkv8ymxevm2g59ub7.png)

As you can see there's a free tier available. Just give your backend a name and click on **Launch**.

![Slash GraphQL backend deployed](https://dev-to-uploads.s3.amazonaws.com/i/x4lk0yc8nhybo28qltao.png)

Soon you'll have a live backend ready to be used. Note down your endpoint (which as you can see is given a randomly unique name; I'm particularly feeling good about *this* one...) since that's where the app would be making all the requests.

You can later access it though from the **Overview** section of your sidebar on the top-left, along with some other statistics about your service.

Now to upload the schema, click on **Create your Schema**.

![Upload schema](https://dev-to-uploads.s3.amazonaws.com/i/yscbvhbqy61qx4spl8i5.png)

Paste it inside the area and hit **Deploy**. That's it, you're done setting up our backend. You can now calmly just focus on building your application.

In case you want to feast your eyes on all the goodies Slash auto-generated from the schema to serve all your needs, you can download the generated schema, by clicking on the **Schema** section of the sidebar, as shown below:

![Download generated schema from Slash](https://dev-to-uploads.s3.amazonaws.com/i/6k9lzvtqaanl1v9w0nzh.png)

## The UI

The UI needs to be customized to account for the new functionalities. There are going to be two new components:

* `AddReviews`
* `ShowReviews`

The first one is where we can submit our review details and the second one is where the app will show all of the reviews. These are going to be implemented by two routes using [React Router](https://reactrouter.com/).

So let's install it:

```bash
npm install --save react-router-dom
```

I'm going to set up the routes in the `App.js` file so let's import the necessary modules for that:

```javascript
import { 
  BrowserRouter as Router, 
  useHistory, 
  Route } from "react-router-dom";
```

And the new components too:

```javascript
import AddReviews from "./Components/Pages/AddReviews";
import ShowReviews from "./Components/Pages/ShowReviews";
```

Now let's set up those two routes:

```javascript
<Route path="/add-reviews/:movieid/:moviename">
  <AddReviews />
</Route>
<Route path="/reviews">
  <ShowReviews />
</Route>
```

The `add-reviews` route would serve the `AddReviews` component and `reviews` would serve `ShowReviews`. Now when using React router in a React app, the `return` body of `App.js` needs to be wrapped in `Router`, which I imported earlier. Also, I'm going to designate `/` to indicate my app's home page. Notice that the home page, i.e. the `App` component itself renders multiple components: `Container`, `UserInput` and `MaterialTable`. These can be conceived as children of the parent component `App`. In this scenario, it makes sense to use something called `React.Fragment` to wrap all of them. What this basically does is that no extra nodes aren't created in the DOM; it's just one component `App`. You can find out more about fragments [here](https://reactjs.org/docs/fragments.html).

So the `return` body looks like this:

```javascript
return (
    <Router>
      <div>
        <Header />
        <Route
          exact
          path="/"
          render={() => (
            <React.Fragment>
              <br></br>
              <Container maxWidth="xs" style={getContainerStyle}>
                <Typography
                  variant="h5"
                  style={{ marginTop: 50, marginBottom: 50 }}
                >
                  Enter a film name or phrase:
                </Typography>

                <UserInput
                  handleInputChange={handleInputChange}
                  handleSubmit={handleSubmit}
                />
              </Container>
              <MaterialTable
                title=""
                columns={[
                  {
                    title: "Name",
                    field: "name",
                    headerStyle: {
                      backgroundColor: "#A5B2FC",
                    },
                  },
                  {
                    title: "Director",
                    field: "director",
                    headerStyle: {
                      backgroundColor: "#A5B2FC",
                    },
                  },
                ]}
                // TODO: should add a progress bar or skeleton
                data={dataForRender}
                options={{
                  search: true,
                  actionsColumnIndex: -1,
                  headerStyle: {
                    backgroundColor: "#A5B2FC",
                  },
                }}
                actions={[
                  {
                    icon: () => <BorderColorIcon />,
                    tooltip: "Write a review",
                    // just using the window object to take to that route
                    // with the movie ID and name passed for running mutation
                    onClick: (event, rowData) =>
                      (window.location.pathname =
                        "/add-reviews/" +
                        rowData.id +
                        "/" +
                        rowData.name.split(" ").join("-")),
                  },
                ]}
                style={{ margin: "5rem" }}
              ></MaterialTable>
            </React.Fragment>
          )}
        ></Route>
        {/* we need some dynamic part in our URL here */}
        <Route path="/add-reviews/:movieid/:moviename">
          <AddReviews />
        </Route>
        <Route path="/reviews">
          <ShowReviews />
        </Route>
      </div>
    </Router>
  );
``` 

You'll notice that I didn't place `Header` inside the fragment. That's because it's a fixed stateless component that is going to be rendered every time in all of the routes. Also, I've used Material UI's typography instead of plain HTML`h5` just as a design sugar; we could do just as well with a plain `<h5>Enter a film name or phrase:</h5>` like before. `Typography` can be imported with the following:

```javascript
import Typography from "@material-ui/core/Typography";
```

I'm using URL parameters (the one's starting with the colon, i.e. `movieid` and `moviename`) to make the movie ID and name available in `AddReviews` page. The ID is going to be necessary in mutation and the `moviename` is strictly for displaying a text saying what film the user is writing a review of.

Also, it'd be nice if there were navigation links in the application header so that we can go back and forth from the reviews page to our home page.

That can be done easily by tweaking our `Header` component a bit.

First I need to import the following:

```javascript
import { Link } from "react-router-dom";
```

I need two navigation links to navigate to two places: *Home* and *Reviews* corresponding to the route `/` and `reviews`. So inside the `Toolbar` I add the following:

```javascript
<Link id="navlink" to="/">
  Home
</Link>
<Link id="navlink" to="/reviews">
  Reviews
</Link>
```

Below is our tweaked `return` body:

```javascript
return (
  <AppBar position="static">
    <Toolbar className="header-toolbar">
      <h2>Film Information</h2>
      <Link id="navlink" to="/">
        Home
      </Link>
      <Link id="navlink" to="/reviews">
        Reviews
      </Link>
    </Toolbar>
  </AppBar>
);
``` 

A bit of CSS styling on `Toolbar` is involved here, in `index.js`:

```css
.header-toolbar {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  /* background-color: #828fd8; */
  color: white;
}

.header-toolbar #navlink {
  margin-left: 3em;
  color: white;
  text-decoration: none;
}
```
 
And here's the `Header` in all its new glories:

![film-reviews header bar](https://dev-to-uploads.s3.amazonaws.com/i/shed4h60qo2wgq7kpsho.png)

Also, in `index.js`, I need to replace the `uri` field of the `ApolloClient` constructor object with the new backend for my app that Slash GraphQL deployed for me:

```javascript
const APOLLO_CLIENT = new ApolloClient({
  uri: "https://hip-spring.us-west-2.aws.cloud.dgraph.io/graphql",
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          queryFilm: {
            merge(_ignored, incoming) {
              return incoming;
            },
          },
        },
      },
    },
  }),
});
```

So, requests of every kind would now go there instead of what the app previously had, `https://play.dgraph.io/graphql`.

Let's go back and take a look at the `return` body of `App.js`. 

We need a way so that upon clicking on a film the user is taken to the `AddReviews` component to write a review for that particular film. That's what I do that with the `actions` prop of `MaterialTable`:

```javascript
actions={[
  {
    icon: () => <BorderColorIcon />,
	tooltip: "Write a review",
	// just using the window object to take to that route
	// with the movie ID and name passed for running mutation
	onClick: (event, rowData) => (window.location.pathname = 
	  "/add-reviews/" +
	  rowData.id +
      "/" +
      rowData.name.split(" ").join("-")),
  },
]}
```

`actions` is just going to be another column in the table. Each row is basically a clickable icon, which is given through the icon property, the value of which is just a component for the icon. Upon hovering, a tooltip is going to give the user a useful prompt. 

`BorderColorIcon` is imported like this:

```javascript
import BorderColorIcon from "@material-ui/icons/BorderColor";
```

I add an `onClick` event handler that would take us to the `add-reviews` route while adding the film ID corresponding to the row the user clicked on to the URL, along with the film name (the film name is just for the UI it won't play any role in the logic). So here we've basically set up a dynamic URL routing for our app! Cool isn't it?

After all this the table looks like the following after a search:

![film-reviews after search](https://dev-to-uploads.s3.amazonaws.com/i/ip1usrtx0m63oy3jf7a1.png)

Let's look at the two components now.

### `AddReviews`

This component is all about mutations. Basically there are going to be two mutations: one where I'd add info about the film that's getting a review written about, and the other are review details--rating and review text. Now, taking into the fact that a film already has a review by a user, that film's data is already in the database so I just need to run mutation for the review. So I set up two constants for each of the scenarios:

```javascript
const ADD_REVIEW = gql`
  mutation($review: AddReviewInput!) {
    addReview(input: [$review]) {
      review {
        text
        rating
        posted_by {
          username
        }
        reviewed_film {
          id
          data {
            name
            id
          }
        }
      }
    }
  }
`;

const ADD_FILMDATA_AND_REVIEW = gql`
  mutation($filmData: [AddFilmDataInput!]!, $review: AddReviewInput!) {
    addFilmData(input: $filmData) {
      filmData {
        id
        data {
          name
          id
        }
      }
    }
    addReview(input: [$review]) {
      review {
        text
        rating
        posted_by {
          username
        }
        reviewed_film {
          id
          data {
            name
            id
          }
        }
      }
    }
  }
`;
```

`ADD_REVIEW` is just for adding a review, while the other is going to add film data too, in case that film doesn't already exist in the database. Notice that `AddFilmDataInput` and `AddReviewInput` are GraphQL [input types](https://graphql.org/learn/schema/#input-types) automatically generated by Dgraph based on the schema, representing the local types `FilmData` and `Review`, corresponding to the variables `$filmData` and `$review`. `$filmData` would need to be supplied with the film ID that we pass from the home page to this component by the dynamic URL. `$review`, you guessed it right, would hold the review details. These are *inputs* for mutations represented as objects, by those two types `AddFilmDataInput` and `AddReviewInput`. Naturally one would have to write them on his/her own, but since I'm using Dgraph, I don't have to. That's another load out of my mind...

Wait... how would I know whether a film is present in my database and make the decision of running either one of those two mutations? I guess I have to check by ID by running a query. If I get a `null` response back, that means there are no films with that ID, i.e. I have to run `ADD_FILMDATA_AND_REVIEW`; otherwise, `ADD_REVIEW`.

Here's the query I'd need:

```javascript
const CHECK_FILM_ID = gql`
  query($id: String!) {
    getFilmData(id: $id) {
      id
    }
  }
`;
```

I set it up using Apollo's `userQuery` hook, just like the search function of `App.js`:

```javascript
const { loading, error, data } = useQuery(CHECK_FILM_ID, {
    variables: { id: movieid },
  });
```

Now I set up the states for the review details that would be submitted by the user:

```javascript
const [reviewText, setReviewText] = useState("");
const [userName, setUserName] = useState("");
const [userRating, setUserRating] = useState(0);
```

Next up is getting an executable mutation using Apollo's `useMutation` hook, a counterpart of the `useQuery` hook:

```javascript
const [addFilmDataAndReview] = useMutation(ADD_FILMDATA_AND_REVIEW);
const [addReview] = useMutation(ADD_REVIEW);
```

I need four event handlers for keeping track of what the user enters as username, rating, review text and not to mention the submission handler...

```javascript
// event handlers
const handleReviewChange = (event) => setReviewText(event.target.value);
const handleNameChange = (event) => setUserName(event.target.value);
const handleRatingChange = (event) => setUserRating(event.target.value * 1);
const handleSubmit = (event) => {
  event.preventDefault();
  // we add filmData only if that film doesn't already exist
  if (data.getFilmData === null) {
    addFilmDataAndReview({
      variables: {
        filmData: [
          {
            id: movieid,
          },
        ],
        review: {
          text: reviewText,
          rating: userRating,
          posted_by: {
            username: userName,
          },
          reviewed_film: {
            id: movieid,
          },
        },
      },
    });
  } else {
    addReview({
      variables: {
        review: {
          text: reviewText,
          rating: userRating,
          posted_by: {
            username: userName,
          },
          reviewed_film: {
            id: movieid,
          },
        },
      },
    });
  }
  // TODO: timeout could be removed
  setTimeout(() => (window.location.pathname = "/"), 1000);
};
```

I check for a `null` response and let the app decide what mutation to run based on that.

Go back and take a look the `addFilmData` mutation again; the value of the variable `$filmData` looks like an array of `AddFilmDataInput`, right? So notice how I'm supplying it as a GraphQL variable here, as an array which contains the movie ID as object's key-value pair. I supply the movie ID as the value of a variable called `movieid`, which is none other than the dynamic part of the URL that contains it. That, and `moviename`, are easily accessible by using the `useParams` hook of React Router that extracts the URL parameters. I store that in the variable `movieid`. It can be imported with:

```javascript
import { useParams } from "react-router-dom";
```

And then I can get get the params using:

```javascript
let { movieid, moviename } = useParams();
```

The rest is pretty straightforward, I have all the user inputs stored in state variables so I'm using them to give the variables their necessary values.

After the mutations have been run, I redirect back to the home page, that is `/`. The `setTimeout` is just for debugging purposes in case something goes wrong and this would allow me to see the error screen before the URL changes.

Next, to set up the necessary "fields" for the user to submit his review, I import the following components from the `material-ui` package:

```javascript
import TextField from "@material-ui/core/TextField";
import TextareaAutosize from "@material-ui/core/TextareaAutosize";
import Button from "@material-ui/core/Button";
import Radio from "@material-ui/core/Radio";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormLabel from "@material-ui/core/FormLabel";
import RadioGroup from "@material-ui/core/RadioGroup";
```

The `return` body of `AddReviews` looks like the following:

```javascript
return (
  <div className="container">
    <Typography variant="h4" style={getPageHeaderStyle}>
      Write your review of <em>{movieName}</em>
    </Typography>
    <Container maxWidth="xs" style={getContainerStyle}>
      <form
        className={styleClass.root}
        noValidate
        autoComplete="off"
        onSubmit={handleSubmit}
      >
        <div>
          <TextField
            label="Username"
            required
            value={userName}
            onChange={handleNameChange}
          />
          <div className="rating-input">
            <FormLabel component="legend" required>
              Rating
            </FormLabel>
            <RadioGroup
              aria-label="movie-rating"
              name="rating"
              value={userRating.toString()}
              onChange={handleRatingChange}
            >
              <FormControlLabel value="1" control={<Radio />} label="1" />
              <FormControlLabel value="2" control={<Radio />} label="2" />
              <FormControlLabel value="3" control={<Radio />} label="3" />
              <FormControlLabel value="4" control={<Radio />} label="4" />
              <FormControlLabel value="5" control={<Radio />} label="5" />
            </RadioGroup>
          </div>
          <TextareaAutosize
            id="review-textarea"
            required
            aria-label="review-text"
            rowsMin={10}
            placeholder="Review..."
            onChange={handleReviewChange}
          />
        </div>
        <div>
          <Button
            type="submit"
            variant="contained"
            color="primary"
            style={{ marginTop: 20 }}
          >
            Submit
          </Button>
        </div>
      </form>
    </Container>
  </div>
);
```

I need to make `moviename` displayable as a space separated string:

```javascript
let movieName = moviename.split("-").join(" ");
```

All this, as I said before, is just for displaying a nice header that says what film is getting reviewed.

Next is just plain HTML `form`, inside which I make use of the components that I imported earlier. `TextField` is where one types in his/her username, a bunch of radio buttons for the 5-star rating system, a re-sizable textarea for where we write our thoughts on the film, and finally the submit button. The container works just like before, placing the whole thing at the centre of the page.

So, after clicking on a film, the user gets greeted with this page:	

![AddReviews page of the app](https://dev-to-uploads.s3.amazonaws.com/i/lf5w0e5mx967hjrkkh20.png)

### `ShowReviews`

This component renders all the information stored in the database, arranged by films, i.e. for each film I show all the reviews submitted by various users.

Here's the query that gets the job done (it's the same as I mentioned when we discussed the schema):

```javascript
const GET_REVIEWS = gql`
  query q2 {
    queryReview {
      reviewed_film {
        id
        data {
          id
          name
        }
        reviews {
          posted_by {
            username
          }
          rating
          text
        }
      }
    }
  }
`;
```

I don't need to explicitly define any state here though, because each time this page is accessed the query would automatically be run and the data we're rendering through the `return` body would change accordingly. So the following is pretty standard stuff:

```javascript
function ShowReviews() {
  const { loading, error, data } = useQuery(GET_REVIEWS);

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

  return (
    <div className="review-content">
      <Typography id="page-title" variant="h2" align="center">
        Reviews
      </Typography>
      {/* map over to render the review details */}
      {data.queryReview.map((content) => (
        <div id="review-details">
          <Typography variant="h4" align="left">
            {content.reviewed_film.data.name}
          </Typography>
          <Divider />
          <br></br>
          {content.reviewed_film.reviews.map((reviewObj) => (
            <Typography variant="subtitle2" align="left">
              {reviewObj.posted_by.username}
              <Typography variant="subtitle1" align="left">
                Rating: {reviewObj.rating}
              </Typography>
              <Typography variant="body1" align="left">
                {reviewObj.text}
              </Typography>
              <br></br>
              <Divider light />
              <br></br>
            </Typography>
          ))}
        </div>
      ))}
    </div>
  );
}
```

I just use JavaScript's `map` method to iterate over the the JSON response tree and render the details.

And `Divider` is just a Material UI component that's nothing but HTML's `<hr>` tag under the hood, strictly for decorative purposes so that the "Reviews" are a bit nicely displayed.

This is how the page looks:

![ShowReviews page of the app](https://dev-to-uploads.s3.amazonaws.com/i/6745r7joy4co2xcvuo33.png)

Here's a GIF showing the flow of the app:

![A GIF of film-reviews app](https://dev-to-uploads.s3.amazonaws.com/i/z2h9htzs0honlk5x6fpc.gif)

## Conclusions

Whew! That was a lot of work wasn't it? But Dgraph took most of the pains away; I just had to focus on the data my app would be handling and how that could be represented by a GraphQL schema. "Thinking in terms of graph" is a saying that goes when building something with GraphQL. I just had to do that; when those pieces are put together and a couple of `types` are nicely defined in my schema, I just needed to deploy it using Slash GraphQL and I had a working API up and running that could handle my data perfectly and allow me to use it however I chose. The rest is just JavaScript and some rudimentary front-end tooling.
 
Another rewarding experience that can be taken from here is that this a pretty close experiment that gives a peek at a real-world application that functions by handling remote and local data. We're using utilities like that everyday, and through this small app, this has been a gentle introduction into the whole orchestration of a large-scale app.

You can check out the entire code of this project that lives on the repo [here](https://github.com/selectiveduplicate/film-reviews).

## References
* [Slash GraphQL](https://dgraph.io/slash-graphql)
* [GraphQL 	Mutations](https://graphql.org/learn/queries/#mutations)
* [Dgraph custom resolvers](https://dgraph.io/docs/graphql/custom/custom-overview/)
* [Dgraph directives](https://dgraph.io/docs/graphql/directives/)