Fetching and displaying chats

Our news feed is working as we expected. Now, we also want to cover chats. As with our feed, we need to query for every chat that the current user (or, in our case, the first user) is associated with.

The initial step is to get the rendering working with some demo chats. Instead of writing the data on our own, as we did in the first chapter, we can now execute the chats query. Then, we can copy the result into the new file as static demo data, before writing the real Query component.

Let's get started, as follows:

  1. Send the GraphQL query. The best options involve Apollo Client Developer Tools, if you already know how they work. Otherwise, you can rely on Postman, as you did previously:
query {
chats {
id
users {
avatar
username
}
}
}

The request looks a bit different from the one we tested with Postman. The chat panel that we are going to build only needs specific data. We do not need to render any messages inside of this panel, so we don't need to request them. A complete chat panel only requires the chat itself, the id, the usernames, and the avatars. Later, we will retrieve all of the messages, too, when viewing a single chat.

                Next, create a new file called Chats.js, next to the Feed.js file.

Copy the complete response over to an array inside of the Chats.js file, as follows. Add it to the top of the file:

const chats = [{
"id": 1,
"users": [{
"id": 1,
"avatar": "/uploads/avatar1.png",
"username": "Test User"
},
{
"id": 2,
"avatar": "/uploads/avatar2.png",
"username": "Test User 2"
}]
}
];
  1. Import React ahead of the chats variable. Otherwise, we will not be able to render any React components:
import React, { Component } from 'react';
  1. Set up the React Component. I have provided the basic markup here. Just copy it beneath the chats variable. I am going to explain the logic of the new component shortly:
export default class Chats extends Component {
usernamesToString(users) {
const userList = users.slice(1);
var usernamesString = '';

for(var i = 0; i < userList.length; i++) {
usernamesString += userList[i].username;
if(i - 1 === userList.length) {
usernamesString += ', ';
}
}
return usernamesString;
}
shorten(text) {
if (text.length > 12) {
return text.substring(0, text.length - 9) + '...';
}

return text;
}
render() {
return (
<div className="chats">
{chats.map((chat, i) =>
<div key={chat.id} className="chat">
<div className="header">
<img src={(chat.users.length > 2 ? '/public/group.png' : chat.users[1].avatar)} />
<div>
<h2>{this.shorten(this.usernamesToString(chat.users))}</h2>
</div>
</div>
</div>
)}
</div>
)
}
}

The component is pretty basic, at the moment. In the render method, we map over all of the chats, and return a new list item for each chat. Each list item has an image, which is taken from the second user of the array, since we defined that the first user in the list is the current user, as long as we have not implemented authentication. We use a group icon if there are more than two users. When we have implemented authentication, and we know the logged-in user, we can take the specific avatar of the user that we are chatting with.

The title displayed inside of the h2 tag at the top of the chat is the name (or names) of the user(s). For this, I have implemented the usernamesToString method, which loops over all of the usernames and concatenates them into a long string. The result is passed into the shorten function, which removes all of the characters of the string that exceed the size of the maximum-twelve characters.

  1. Our new component needs some styling. Copy the new CSS to our style.css file.

To save the file size in our CSS file, replace the two post header styles to also cover the style of the chats, as follows:

.post .header > *, .chats .chat .header > * {
display: inline-block;
vertical-align: middle;
}

.post .header img, .chats .chat .header img {
width: 50px;
margin: 5px;
}

We must append the following CSS to the bottom of the style.css file:

.chats {
background-color: #eee;
width: 200px;
height: 100%;
position: fixed;
top: 0;
right: 0;
border-left: 1px solid #c3c3c3;
}

.chats .chat {
cursor: pointer;
}

.chats .chat .header > div {
width: calc(100% - 65px);
font-size: 16px;
margin-left: 5px;
}

.chats .chat .header h2, .chats .chat .header span {
color: #333;
font-size: 16px;
margin: 0;
}

.chats .chat .header span {
color: #333;
font-size: 12px;
}

  1. To get the code working, we must also import the Chats class in our App.js file:
import Chats from './Chats';
  1. Render the Chats class inside of the render method, beneath the Feed class inside of the App.js file.

The current code generates the following screenshot:

source: https://www.vecteezy.com/

On the right-hand side, you can see the chats panel that we have just implemented. Every chat is listed there as a separate row.

The result isn't bad, but it would be much more helpful to at least have the last message of every chat beneath the username, so that you could directly see the last content of your conversations.

Just follow these instructions to get the last message into the chats panel:

  1. The easiest way to do this would be to add the messages to our query again, but querying all of the messages for every chat that we wanted to display in the panel would not make much sense. Instead, we will add a new property to the chat entity, called lastMessage. That way, we will only get the newest message. We will add the new field to the GraphQL schema of our chat type, in the back end code, as follows:
lastMessage: Message

Of course, we must also implement a function that retrieves the lastMessage field.

  1. Our new resolvers.js function orders all of the chat messages by id, and takes the first one. By definition, this should be the latest message in our chat. We need to resolve the promise on our own and return the first element of the array, since we expect to return only one message object. If you return the promise directly, you will receive null in the response from the server, because an array is not a valid response for a single message entity:
lastMessage(chat, args, context) {
return chat.getMessages({limit: 1, order: [['id', 'DESC']]}).then((message) => {
return message[0];
});
},
  1. You can add the new property to our static data, inside of Chats.js. Rerunning the query (as we did in step 1) would also be possible:
"lastMessage": {
"text": "This is a third test message."
}
      1. We can render the new message with a simple span tag beneath the h2 of the username. Copy it directly into the render method, inside of our Chats class:
      <span>{this.shorten(chat.lastMessage.text)}</span>

      The result of the preceding changes renders every chat row with the last message inside of the chat. This looks like the following screenshot:

      source: https://www.vecteezy.com/

      If you are not that happy with the design, feel free to change it as you wish. As I am not a designer, I won't put too much effort into it. 

      Since everything is displayed correctly from our test data, we can introduce the Query component, in order to fetch all of the data from our GraphQL API. We can remove the chats array. Then, we will import all of the dependencies and parse the GraphQL query, as in the following code:

      import gql from 'graphql-tag';
      import { Query } from 'react-apollo';

      const GET_CHATS = gql`{
      chats {
      id
      users {
      id
      avatar
      username
      }
      lastMessage {
      text
      }
      }
      }`;

      Our new render method does not change much. We just include the Apollo Query component, as follows:

      <div className="chats">
      <Query query={GET_CHATS}>
      {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return error.message;

      const { chats } = data;

      return chats.map((chat, i) =>
      <div key={"chat" + chat.id} className="chat">
      <div className="header">
      <img src={(chat.users.length > 2 ? '/public/group.png' :
      chat.users[1].avatar)} />
      <div>
      <h2>{this.shorten(this.usernamesToString(chat.users))}
      </h2>
      <span>{chat.lastMessage &&
      this.shorten(chat.lastMessage.text)}</span>
      </div>
      </div>
      </div>
      )
      }}
      </Query>
      </div>

      Be sure to render the div with the chats class name first, and after that, the chat loop function. The reason for this is that we want to show the user the gray panel, with a loading indicator. If you do it the other way round, the gray panel will be displayed when the response is successfully received. You should have run the addChat mutation from the previous chapter through Postman. Otherwise, there will be no chats to query for, and the panel will be empty. You have to execute this mutation also for any following chapter because we are not going to implement a special button for this functionality.

      One obvious question that you might have is as follows: How can we create new chats with other users? We will focus on this issue when we have implemented the authentication properly, and can visit users' profiles. Next, we want to display the chat messages after opening a specific chat.

      ..................Content has been hidden....................

      You can't read the all page of ebook, please click here login for view all page.
      Reset
      3.135.246.193