Learning GraphQL by building a chat application - Part 2
Tags: graphql, react, apolloclient
Published: 2020-03-09 13:18:15

---
title: Learning GraphQL by building a chat application - Part 2
published: true
description: Learning GraphQL by building a chat application
tags: graphql, reactjs, apolloClient
cover_image: https://res.cloudinary.com/dje4crtui/image/upload/v1595010225/1_rfcKecKMtiD0WaALeTCkag_qladep.png
series: GraphQL Chat Application
---

Continuing the previous [article](https://dev.to/ricardo93borges/learning-graphql-by-building-a-chat-application-part-1-1n2k) we are going to build our chat application front-end, this article assumes you're familiar with ReactJS, so we'll focus more on GraphQL and Apollo and less on ReactJS, so before we start I suggest you clone the [project repository] (https://github.com/ricardo93borges/graphql-chat-client). Also, you'll notice that there is room for improving usability and style, because, as I said, we are more concerned to use GraphQL with Apollo than any other aspect of this application.

## Initial setup

Let's get started, we'll develop three features: Login, contacts list, and conversation. The application flow is very simple, the user, after login, will choose a contact in a contacts list to start a conversation and will start sending messages (login > contacts list > chat).

The quick way to start our application would use Apollo Boost, but it doesn't have support for subscriptions, so we need to configure the Apollo Client manually, we'll put all this configuration in `api.js` file:

```js
// src/api.js 

import { InMemoryCache } from 'apollo-cache-inmemory'
import { getMainDefinition } from 'apollo-utilities'
import { WebSocketLink } from 'apollo-link-ws'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { split } from 'apollo-link'

/**
* Web socket configuration that we'll use in our subscriptions
* We can send connection params in the `options` property, we'll see another way
* to send these params later
*/
const wsLink = new WebSocketLink({
  uri: process.env.REACT_APP_API_WS_URL,
  options: {
    reconnect: true,
    connectionParams: () => ({
      Authorization: `Bearer ${localStorage.getItem('token')}`
    })
  }
})

/**
* HTTP configuration that we'll use in any other request
*/
const httpLink = new HttpLink({
  uri: process.env.REACT_APP_API_URL,
  // It is possible to set headers here too:
  headers: {
    Authorization: `Bearer ${localStorage.getItem('token')}`
  }
})

const link = split(({ query }) => {
  const definition = getMainDefinition(query)
  return (
    definition.kind === 'OperationDefinition' &&
    definition.operation === 'subscription'
  )
},
  wsLink,
  httpLink
)

export const client = new ApolloClient({
  link,
  cache: new InMemoryCache()
})

```

Don't forget to edit the environment variables in the `.env` file to match your local configurations, there are only two, probably you'll use the same values that are in the `.env.sample` file.

Next, in the `index.js` file we import the configured Apollo Client and provide it to the `<ApolloProvider>` component:

```js
// src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloProvider } from '@apollo/react-hooks'
import * as serviceWorker from './serviceWorker'
import { client } from './api'
import { App } from './App'

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

serviceWorker.unregister()
``` 

In the `<App>` component there are only our routes:

```js
// src/App.js

import 'milligram'
import React from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import { Header } from './components/Header'
import { Chat } from './pages/chat'
import { Login } from './pages/login'
import { Contacts } from './pages/contacts'

export const App = () => {
  return (
    <div className='container'>
      <BrowserRouter forceRefresh={true}>
        <Header />
        <Switch>
          <Route exact path='/' component={Login} />
          <Route path='/login' component={Login} />
          <Route path='/contacts' component={Contacts} />
          <Route path='/chat/:id' component={Chat} />
        </Switch>
      </BrowserRouter>
    </div>
  )
}
```

## Apollo Client's React Hooks

Before we continue, some code snippets will have some parts omitted, but I put a link to complete code after the snippet when needed.

Apollo client provides three hooks for queries, mutations, and subscriptions, the first hook we'll use is `useMutation` on the login page, so the user will enter his email, password and click on the login button, then the `LOGIN` mutation will be executed:

```js
// src/pages/login/index.js

import React, { useEffect } from 'react'
import { useMutation } from '@apollo/react-hooks'
import { LOGIN } from './mutations'

export const Login = ({ history }) => {
  let email
  let password
  const [login, { data }] = useMutation(LOGIN)

  return (
    <div className='row'>
      <div className='column column-50 column-offset-25'>
        <form>
          {/* ... */}
          <div className='row'>
            <div className='column column-50 column-offset-25'>
              <button
                className='float-right'
                onClick={e => {
                  e.preventDefault()
                  login({ variables: { email: email.value, password: password.value } })
                }}
              >
                Login
              </button>
            </div>
          </div>
        </form>
      </div>
    </div>
  )
}
```
[login page component](https://github.com/ricardo93borges/graphql-chat-client/blob/master/src/pages/login/index.js)

Login mutation:

```js
import { gql } from 'apollo-boost'

export const LOGIN = gql`
  mutation login($email: String!, $password: String!) {
    login(email: $email, password: $password)
  }
`
```

It is simple like that, you call `useMutation`, pass it a mutation string that represents the mutation and it returns a function and the possible data from the mutation, in this case, `login` and `data`, you call the `login` function with some variables and it is done.

We are not creating a register page, I'll leave this challenge for you, or you can create a user on GraphQL playground.

Moving on to the contacts page we will use the `useQuery` hook, which is quite straightforward we pass it a GraphQL query string, when the component renders, `useQuery` returns an object from Apollo Client that contains `loading`, `error`, and `data` properties:

```js
// src/pages/contacts/index.js

import React from 'react'
import { useQuery } from '@apollo/react-hooks'
import { USERS } from './queries'

export const Contacts = ({ history }) => {
  const { loading, error, data } = useQuery(USERS, {
    context: {
      headers: {
        Authorization: `Bearer ${localStorage.getItem('token')}`
      }
    }
  })

  if (loading) return 'loading ...'

  if (error) return `Error: ${error.message}`

  return (
    <>
      {data.users.map(user =>
        <div key={user.id} className='row'>
          <div className='column' />
          <div className='column' style={{ textAlign: 'center' }}>
            <button
              className='button button-outline'
              onClick={() => history.push(`/chat/${user.id}`)}
            >
              {user.name}
            </button>
          </div>
          <div className='column' />
        </div>
      )}
    </>
  )
}
```

This time besides the `USERS` query string we pass it the Bearer token, `useQuery`, like the other hooks, accepts other arguments, refer to the [documentation](https://www.apollographql.com/docs/react/data/queries/) for more details.

Here is the `USERS` query:

```js
// src/pages/contacts/queries.js

import { gql } from 'apollo-boost'

export const USERS = gql`
  query Users {
    users {
      id
      name
      email
    }
  } 
`
```

The next page is the chat page, there are more components on this page than in the others, let's start with the main component:

```js
// src/pages/chat/index.js

import React from 'react'
import { useQuery } from '@apollo/react-hooks'
import { CONVERSATION } from './queries'
import { MESSAGES_SUBSCRIPTION } from './subscription'
import { MessageList } from './components/MessageList'
import { SendForm } from './components/SendForm'

const handleNewMessage = (subscribeToMore) => {
  subscribeToMore({
    document: MESSAGES_SUBSCRIPTION,
    updateQuery: (prev, { subscriptionData }) => {
      if (!subscriptionData.data) return prev
      const newMessage = subscriptionData.data.messageSent

      return {
        conversation: [...prev.conversation, newMessage]
      }
    }
  })
}

export const Chat = ({ match }) => {
  const options = {
    context: {
      headers: {
        Authorization: `Bearer ${localStorage.getItem('token')}`
      }
    },
    variables: {
      cursor: '0',
      receiverId: match.params.id
    },
  }

  const { subscribeToMore, ...result } = useQuery(CONVERSATION, options)

  return (
    <>
      <div
        className='row'
        style={{
          height: window.innerHeight - 250,
          overflowY: 'scroll',
          marginBottom: 10
        }}>
        <div className='column'>
          <MessageList
            {...result}
            subscribeToNewMessages={() => handleNewMessage(subscribeToMore)}
          />
        </div>
      </div>
      <SendForm receiverId={match.params.id} />
    </>
  )
}
```

Every time a user sends a message we want to show that message along with the previous ones, to do that we can use the function `subscribeToMore` that is available on every query result and will be called every time the subscription returns. The function `handleNewMessage` will handle the new messages inserting them into the list of messages.

Below are the GraphQL queries, mutations, subscriptions, and fragments used in the chat page, a fragment is a shared piece of query logic:

```js
// src/pages/chat/queries.js

import { gql } from 'apollo-boost'
import { MESSAGE } from './fragments'

export const MESSAGES = gql`
  query Messages($cursor: String!) {
    messages(cursor: $cursor) {
      ...Message
    }
  } 
  ${MESSAGE}
`

export const CONVERSATION = gql`
  query Conversation($cursor: String!, $receiverId: ID!) {
    conversation(cursor: $cursor, receiverId: $receiverId) {
      ...Message
    }
  } 
  ${MESSAGE}
`
```

```js
// src/pages/chat/subscription.js

import { gql } from 'apollo-boost'
import { MESSAGE } from './fragments'

export const MESSAGES_SUBSCRIPTION = gql`
  subscription messageSent {
    messageSent {
      ...Message
    }
  }
  ${MESSAGE}
`
```

```js
// src/pages/chat/mutations.js

import { gql } from 'apollo-boost'
import { MESSAGE } from './fragments'

export const SEND_MESSAGE = gql`
  mutation sendMessage($sendMessageInput: SendMessageInput!) {
    sendMessage(sendMessageInput: $sendMessageInput){
      ...Message
    }
  }
  ${MESSAGE}
`
```

```js
// src/pages/chat/fragments.js

import { gql } from 'apollo-boost'

export const USER = gql`
  fragment User on User {
    id
    name
    email
  }
`

export const MESSAGE = gql`
  fragment Message on Message {
    id
    message
    sender {
      ...User
    }
    receiver {
      ...User
    }
  }
  ${USER}
`
```

The `MessageList` component is responsible for rendering the messages:

```js
// src/pages/chat/components/MessageList.js

import React, { useEffect, useState } from 'react'
import { MessageItemSender } from './MessageItemSender'
import { MessageItemReceiver } from './MessageItemReceiver'
import { decode } from '../../../session'

export const MessageList = (props) => {
  const [user, setUser] = useState(null)

  useEffect(() => {
    setUser(decode())
    props.subscribeToNewMessages()
  }, [])

  if (!props.data) { return <p>loading...</p> }

  return props.data.conversation.map(message =>
    user.id === parseInt(message.sender.id, 10)
      ? <MessageItemSender key={message.id} message={message} />
      : <MessageItemReceiver key={message.id} message={message} />
  )
}
```

You can find the `MessageItemSender` and `MessageItemReceiver` [here](https://github.com/ricardo93borges/graphql-chat-client/tree/master/src/pages/chat/components).

The last component is the `SendForm` it is responsible for sending messages, and its behavior is similar to the login component:

```js
// src/pages/chat/components/SendForm.js

import React from 'react'
import { useMutation } from '@apollo/react-hooks'
import { SEND_MESSAGE } from '../mutations'

export const SendForm = ({ receiverId }) => {
  let input
  const [sendMessage] = useMutation(SEND_MESSAGE)

  return (
    <div className='row'>
      <div className='column column-80'>
        <input type='text' ref={node => { input = node }} />
      </div>
      <div className='column column-20'>
        <button onClick={e => {
          e.preventDefault()
          sendMessage({
            variables: {
              sendMessageInput: {
                receiverId,
                message: input.value
              }
            }
          })
        }}
        >
          Send
      </button>
      </div>
    </div>
  )
}

```

This is it, to see the app working you could create two users and login with each account in different browsers and send messages to each other.