We will next implement a React-app which uses the GraphQL server we created.

The current code of the server can be found on Github, branch part8-3.

In theory, we can use GraphQL with HTTP POST -requests. The following shows an example of this with Postman.

fullstack content

The communication works by sending HTTP POST -requests to http://localhost:4000/graphql. The query itself is a string sent as the value of the key query.

We could take care of the communication between the React-app and GraphQl by using Axios. However most of the time it is not very sensible to do so. It is a better idea to use a higher order library capable of abstracting the unnecessary details of the communication.

At the moment there are two good options: Relay by Facebook and Apollo Client. From these two Apollo is absolutely more popular, and we will also be using it.

Apollo client

Create a new React-app and install the dependencies required by Apollo client.

npm install apollo-boost react-apollo graphql --save

We'll start with the following code for our application.

import React from 'react'
import ReactDOM from 'react-dom'

import ApolloClient, { gql } from 'apollo-boost'

const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql'
})

const query = gql`
{
  allPersons  {
    name,
    phone,
    address {
      street,
      city
    }
    id
  }
}
`

client.query({ query })
  .then((response) => {
    console.log(response.data)
  })

const App = () => {
  return <div>
    test
  </div>
}

ReactDOM.render(<App />, document.getElementById('root'))

The beginning of the code creates a new client - object, which is then used to send a query to the server:

client.query({ query })
  .then((response) => {
    console.log(response.data)
  })

The servers response is printed to the console:

fullstack content

The application can communicate with a GraphQL server using the client object. The client can be made accessible for all components of the application by wrapping the App component with ApolloProvider.

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'import ApolloClient, { gql } from 'apollo-boost'
import { ApolloProvider } from 'react-apollo'
const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql'
})

ReactDOM.render(
  <ApolloProvider client={client} >    <App />  </ApolloProvider>,   document.getElementById('root')
)

Query-component

We are ready to implement the main view of the application, which shows a list of phone numbers.

Apollo Client offers few alternative ways for making queries. At the moment (this part was translated 22.6.2019) the predominant way is to use a Query component.

The code for the component App, which makes the query, is as follows:

import React from 'react'
import { Query } from 'react-apollo'
import { gql } from 'apollo-boost'

const ALL_PERSONS = gql`
{
  allPersons  {
    name
    phone
    id
  }
}
`

const App = () => {
  return <Query query={ALL_PERSONS}>
    {(result) => { 
      if ( result.loading ) {
        return <div>loading...</div>
      }
      return (
        <div>
          {result.data.allPersons.map(p => p.name).join(', ')}
        </div>
      )
    }}
  </Query>
}

export default App

The code does seem a bit confusing. The core of the code is the component Query. The query to be made is in the variable ALL_PERSONS. The query is given to the Query component as a parameter. Within the tags of the Query component is a function, which returns the actual JSX to be rendered. A parameter of the function, results, contains the result of the GraphQL query.

The result, the object in parameter results, has multiple fields. The field loading has the value true, if there is no response to the query yet. In this case the code to be rendered is

if ( result.loading ) {
  return <div>loading...</div>
}

When the result is ready, response to the query allPersons is taken from the field data, and the names in the phonebook are rendered to the screen.

<div>
  {result.data.allPersons.map(p => p.name).join(', ')}
</div>

To clean the solution up a bit, let's separate rendering the list of persons into its own component Persons. The component App becomes:

const App = () => {
  return (
    <Query query={ALL_PERSONS}>
      {(result) => <Persons result={result} />}
    </Query>
  )
}

So App sends the query results to the Persons component as props:

const Persons = ({ result }) => {
  if (result.loading) {
    return <div>loading...</div>
  }

  const persons = result.data.allPersons 

  return (
    <div>
      <h2>Persons</h2>
      {persons.map(p =>
        <div key={p.name}>
          {p.name} {p.phone}
        </div>  
      )}
    </div>
  )
}

Named queries and variables

Let's implement functionality for viewing the address details of a person. The findPerson query is well suited for this.

The queries we did in the last chapter had the parameter hardcoded into the query:

query {
  findPerson(name: "Arto Hellas") {
    phone 
    city 
    street
    id
  }
}

When we do queries programmatically, we must be able to give them parameters dynamically.

GraphQL variables are well suited for this. To be able to use variables, we must also name our queries.

query findPersonByName($nameToSearch: String!) {
  findPerson(name: $nameToSearch) {
    name
    phone 
    address {
      street
      city
    }
  }
}

The name of the query is findPersonByName, and it is given a string $nameToSearch as a parameter.

It is also possible to do queries with parameters with the GraphQL Playground. The parameters are given in Query variables:

fullstack content

The component we just used,Query, is not optimal for our purposes, because we would like to make the query only when a user wants to see the details of a person.

One way would be to use the query method of the client object. All components of the application can access the query object via the ApolloConsumer component.

Let's modify the App component to fetch a reference to the query object via ApolloConsumer, and pass it on to the Persons component.

import { Query, ApolloConsumer } from 'react-apollo'
// ...

const App = () => {
  return (
    <ApolloConsumer>
      {(client => 
        <Query query={ALL_PERSONS}>
          {(result) => 
            <Persons result={result} client={client} /> 
          }
        </Query> 
      )}
    </ApolloConsumer>
  )
}

Changes to the Persons component are as follows:

const FIND_PERSON = gql`  query findPersonByName($nameToSearch: String!) {    findPerson(name: $nameToSearch) {      name      phone       id      address {        street        city      }    }  }`
const Persons = ({ result, client }) => {
  const [person, setPerson] = useState(null)
  if (result.loading) {
    return <div>loading...</div>
  }

  const showPerson = async (name) => {    const { data } = await client.query({      query: FIND_PERSON,      variables: { nameToSearch: name }    })    setPerson(data.findPerson)  }
  if (person) {    return(      <div>        <h2>{person.name}</h2>        <div>{person.address.street} {person.address.city}</div>        <div>{person.phone}</div>        <button onClick={() => setPerson(null)}>close</button>      </div>    )  }
  return (
    <div>
      <h2>Persons</h2>
      {result.data.allPersons.map(p =>
        <div key={p.name}>
          {p.name} {p.phone}
          <button onClick={() => showPerson(p.name)} >            show address          </button>         </div>  
      )}
    </div>
  )
}

If the button next to person's details is pressed, the component makes a GraphQL query for the person's details and saves the response to the component state person:

const showPerson = async (name) => {
  const { data } = await client.query({
    query: FIND_PERSON,
    variables: { nameToSearch: name }
  })

  setPerson(data.findPerson)
}

If the state person has a value, instead of showing a list of all persons, only the details of one person are shown.

fullstack content

The solution is not the neatest possible, but it is good enough for us.

The current code of the application can be found on Github branch part8-1.

Cache

When we do multiple queries for example the address details of Arto Hellas, we notice something interesting: The query to the backend is done only the first time around. After this, despite of the same query being done again by the code, the query is not sent to the backend.

fullstack content

Apollo client saves the responses of queries to cache. To optimize performance if the response to a query is already in the cache, the query is not sent to the server at all.

It is possible to install Apollo Client devtools to Chrome to view the state of the cache.

fullstack content

Data in the cache is organized by query. Because Person objects have an identifying field id which is type ID, if the same object is returned by multiple queries, Apollo is able to combine them into one. Because of this, doing findPerson queries for the address details of Arto Hellas has updated the address details also for the query allPersons.

Mutation-component

Let's implement functionality for adding new persons. The mutation component offers suitable tools for this. In the previous chapter we hardcoded the parameters for mutations. Now we need a version of the addPerson mutation which uses variables.

const CREATE_PERSON = gql`
  mutation createPerson($name: String!, $street: String!, $city: String!, $phone: String) {
    addPerson(
      name: $name,
      street: $street,
      city: $city,
      phone: $phone
    ) {
      name
      phone
      id
      address {
        street
        city
      }
    }
  }
`

The App component changes like so:

const App = () => {
  return (
    <div>
      <ApolloConsumer>
        {(client) => 
          <Query query={ALL_PERSONS}>
            {(result) => 
              <Persons result={result} client={client} />
            }
          </Query> 
        }
      </ApolloConsumer>
      <h2>create new</h2>      <Mutation mutation={CREATE_PERSON}>        {(addPerson) =>          <PersonForm            addPerson={addPerson}          />        }      </Mutation>    </div>
  )
}

Within the tags of the Mutation component is a function, which returns a PersonForm component. The parameter addPerson is a function, which does the mutation.

The component containing the form is nothing special.

const PersonForm = (props) => {
  const [name, setName] = useState('')
  const [phone, setPhone] = useState('')
  const [street, setStreet] = useState('')
  const [city, setCity] = useState('')

  const submit = async (e) => {
    e.preventDefault()
    await props.addPerson({
      variables: { name, phone, street, city }
    })

    setName('')
    setPhone('')
    setStreet('')
    setCity('')
  }

  return (
    <div>
      <form onSubmit={submit}>
        <div>
          name <input
            value={name}
            onChange={({ target }) => setName(target.value)}
          />
        </div>
        <div>
          phone <input
            value={phone}
            onChange={({ target }) => setPhone(target.value)}
          />
        </div>
        <div>
          street <input
            value={street}
            onChange={({ target }) => setStreet(target.value)}
          />
        </div>
        <div>
          city <input
            value={city}
            onChange={({ target }) => setCity(target.value)}
          />
        </div>
        <button type='submit'>add!</button>
      </form>
    </div>
  )
}

New persons are added just fine, but the screen is not updated. The reason being that Apollo Client cannot automatically update the cache of an application, so it still contains the state from before the mutation. We could update the screen by reloading the page, as the cache is emptied when the page is reloaded. However there must be a better way to do this.

Updating the cache

There are few different solutions for this. One way is to make the query for all persons poll the server, or make the query repeatedly.

The change is small. Let's set the query to poll every two seconds:

const App = () => {
  return (
    <div>
      <ApolloConsumer>
        {(client) => 
          <Query query={ALL_PERSONS} pollInterval={2000}>            {(result) =>
              <Persons result={result} client={client} />
            }
          </Query> 
        }
      </ApolloConsumer>

      <h2>create new</h2>
      <Mutation mutation={createPerson} >
        {(addPerson) =>
          <PersonForm
            addPerson={addPerson}
          />
        }
      </Mutation>
    </div>
  )
}

The solution is simple, and every time a user adds a new person, it appears immediately on the screens of all users.

The bad side of the solution is all the pointless web traffic.

Another easy way to synchronize the cache is to declare, that the ALL_PERSONS query should be done again when a new person is added. This can be done with the refetchQueries props of the Mutation component:

const App = () => {
  return (
    <div>
      <ApolloConsumer>
        {(client) => 
          <Query query={allPersons}>
            {(result) =>
              <Persons result={result} client={client} 
            />}
          </Query> 
        }
      </ApolloConsumer>

      <h2>create new</h2>
      <Mutation
        mutation={CREATE_PERSON} 
        refetchQueries={[{ query: ALL_PERSONS }]}      >
        {(addPerson) =>
          <PersonForm
            addPerson={addPerson}
          />
        }
      </Mutation>
    </div>
  )
}

The pros and cons of this solution are almost opposite of the previous one. There is no extra web traffic, because queries are not done just in case. However if one user now updates the state of the server, the changes do not show to other users immediately.

There are other ways to update the cache. More about those later in this part.

NB Apollo Client devtools seems to have some bugs. At some point it stops updating the state of the cache. If you encounter this issue, open the application in a new tab.

The current code of the application can be found on Github branch part8-2.

Handling mutation error messages

If we try to create an invalid person, it results in an error.

fullstack content

The error should be handled. One way to do this is to register an errorhandler to the mutation using the onError props.

const App = () => {
  const [errorMessage, setErrorMessage] = useState(null)  const handleError = (error) => {    setErrorMessage(error.graphQLErrors[0].message)    setTimeout(() => {      setErrorMessage(null)    }, 10000)  }
  return (
    <div>
      {errorMessage &&        <div style={{color: 'red'}}>          {errorMessage}        </div>      }      <ApolloConsumer>
        // ...
      </ApolloConsumer>

      <h2>create new</h2>
      <Mutation
        mutation={createPerson} 
        refetchQueries={[{ query: allPersons }]}
        onError={handleError}      >
        {(addPerson) =>
          <PersonForm
            addPerson={addPerson}
          />
        }
      </Mutation>
    </div>
  )
}

Now the user is informed about an error with a simple notification.

fullstack content

The current code of the application can be found on Github branch part8-3.

Updating a phone number

Let's add the possibility to change the phone numbers of persons to our application. The solutions is almost identical to the one we used for adding new persons.

Again, the mutation requires parameters.

const EDIT_NUMBER = gql`
mutation editNumber($name: String!, $phone: String!) {
  editNumber(name: $name, phone: $phone)  {
    name
    phone
    address {
      street
      city
    }
    id
  }
}
`

Let's add this to the App-component:

const App = () => {
  // ...
  return (
    <div>
      {errorMessage && ... }
      <ApolloConsumer>
        // ...
      </ApolloConsumer>
      
      <h2>create new</h2>
      <Mutation mutation={CREATE_PERSON}>
        // ...
      </Mutation>

      <h2>change number</h2>      <Mutation        mutation={EDIT_NUMBER}      >        {(editNumber) =>          <PhoneForm            editNumber={editNumber}          />        }      </Mutation>       </div>
  )
}

PhoneForm, the component executing the mutation, is straightforward. It asks for the name of a person using a form, and calls editNumber, the function doing the mutation:

const PhoneForm = (props) => {
  const [name, setName] = useState('')
  const [phone, setPhone] = useState('')

  const submit = async (e) => {
    e.preventDefault()

    await props.editNumber({
      variables: { name, phone }
    })

    setName('')
    setPhone('')
  }

  return (
    <div>
      <form onSubmit={submit}>
        <div>
          name <input
            value={name}
            onChange={({ target }) => setName(target.value)}
          />
        </div>
        <div>
          phone <input
            value={phone}
            onChange={({ target }) => setPhone(target.value)}
          />
        </div>
        <button type='submit'>change number</button>
      </form>
    </div>
  )
}

It looks bleak, but it works:

fullstack content

When a number is changed, surprisingly the list of names and numbers rendered by the component Persons is also automatically updated. This is due to two factors. First, because persons have identifying field type ID, the person is updated in the cache when the update operation is done. The second reason for the automatic update of the view is, that the data returned by a query done with the Query component notices the changes in the cache and updates itself automatically. This is true only for the objects originally returned by the query, not for completely new objects added to the cache, which would be returned from a query done again.

If we try to change the phone number of a nonexisting name, nothing seems to happen. The reason for this is, that if a person corresponding to the name cannot be found, the response to the query is null:

fullstack content

The current code of the application can be found on Github branch part8-4

Apollo Client and the applications state

In our example, management of the applications state has mostly become the responsibility of Apollo Client. This is quite typical solution for GraphQL-applications. Our example uses the state of the React components only to manage the state of a form and to show error notifications. When using GraphQL it can be, that there are no more justifiable reasons to move the management of the applications state to Redux at all.

When necessary Apollo enables saving the applications local state to Apollo cache.

Render props

GraphQL components Query, Mutation and ApolloConsumer follow the so called render props principle. A component following this principle is given, as props or as a child between its tags (which technically is also a props), a function which defines how the component is rendered. With the render props -principle it is possible to move data or function references to the component responsible for rendering.

The Render props -principle has been quite popular. For example react router we used in part 7 uses it. Using the component Route of the React router it is defined what the application renders when the browser is in a certain url. The following defines, that if the url is /notes, the component Notes is rendered, and if the url is for example /notes/10, a Note component which has been given id 10 as a parameter is rendered.

<Router>
  <div>
    // ...
    <Route exact path='/notes' render={() => 
      <Notes notes={notes} />
    } />    
    <Route exact path='/notes/:id' render={({ match }) =>
      <Note note={noteById(match.params.id)} />
    } />
  </div>
</Router>

The component corresponding to the urls have been defined as render props. It is possible to pass on information to rendered component with the render props -function. For example the page of a single note gets the note corresponding to its url as props.

I myself am not a huge fan of render props. In connection to React router they suffice, but especially in connection to GraphQL using them feels really bad.

In our example we must, regrettably, wrap the Persons component to two render props -components:

<ApolloConsumer>
  {(client) => 
    <Query query={allPersons}>
      {(result) => <Persons result={result} client={client} />}
    </Query> 
  }
</ApolloConsumer>

Within a few weeks we can however expect some changes, and an API for using queries and mutations with hooks will be added to Apollo.

More generally the trend is to replace the need for render props with hooks.

Apollo with hooks

There is already a beta release available for Apollo Client 3.0.0 that has the hook support. Let us try it out.

npm install --save react-apollo@3.0.0-beta.2

There is currently (22.6.2019) no documentation how to use Apollo hooks, this blog is one of the rare examples found by Google.

A small change is needed to index.js after the new version is installed:

import React from 'react'
import ReactDOM from 'react-dom'
import ApolloClient from 'apollo-boost'
import { ApolloProvider } from "@apollo/react-hooks"
import App from './App'

const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql'
})

ReactDOM.render(
  <ApolloProvider client={client} >
    <App />
  </ApolloProvider>, 
  document.getElementById('root')
)

Let's change the Persons component so that it uses the useApolloClient-hook.

import React,  { useState } from 'react'
import { gql } from 'apollo-boost'
import { useApolloClient } from '@apollo/react-hooks'
// ...

const Persons = ({ result }) => {  const client = useApolloClient()  // ...
}

The App component is simplified, as we can remove the render props -component ApolloConsumer:

const App = () => {

  return(
    <div>
      {errorMessage &&
        <div style={{ color: 'red' }}>
          {errorMessage}
        </div>
      }
      <Query query={ALL_PERSONS}>        {(result) => <Persons result={result} />}      </Query>       // ...
    </div>
  )
}

Let's get rid of the Query -component with the useQuery hook. The App becomes simpler still:

import { useQuery } from '@apollo/react-hooks'
const App = () => {
  const persons = useQuery(ALL_PERSONS)
  // ...

  return (
    <div>
      {errorMessage &&
        <div style={{ color: 'red' }}>
          {errorMessage}
        </div>
      }

      <Persons result={persons} />
      <Mutation
        mutation={createPerson} 
        refetchQueries={[{ query: allPersons }]}
        onError={handleError}
      >
        {(addPerson) =>
          <PersonForm
            addPerson={addPerson}
          />
        }
      </Mutation>
      // ...
    </div>
  )
}

We can replace the Mutation -components with the useMutation hook. The final form of the App -component is as follows:

import { useQuery, useMutation } from '@apollo/react-hooks'
const App = () => {
  const result = useQuery(ALL_PERSONS)

  const [errorMessage, setErrorMessage] = useState(null)

  const handleError = (error) => {
    // ...
  }

  const [addPerson] = useMutation(CREATE_PERSON, {    onError: handleError,    refetchQueries: [{ query: ALL_PERSONS }]  })
  const [editNumber] = useMutation(EDIT_NUMBER)
  return (
    <div>
      {errorMessage &&
        <div style={{ color: 'red' }}>
          {errorMessage}
        </div>
      }
      <Persons result={result} />

      <h2>create new</h2>
      <PersonForm addPerson={addPerson} />
      <h2>change number</h2>
      <PhoneForm editNumber={editNumber} />    </div>
  )
}

Note that the hook useMutation returns an array. The first value in that array is a function that we can use to trigger the mutation. The second value of this array is an object that will give you the loading and error state of the mutation, but we are currently not using those fields.

The final result is really so much cleaner than the mess using the render props -components. We can join Ryan Florence in the opinion he stated in React Conf 2018 90% Cleaner React With Hooks.

The code of the application which uses hooks can be found on Github branch part8-5.