Make CometChat work with Wolox React Chat Widget

In this tutorial, you will learn how to build a chat widget powered by CometChat. You will make use of the Wolox Chat widget [https://github.

Idorenyin Obong • Apr 21, 2020

In this tutorial, you will learn how to build a chat widget powered by CometChat. You will make use of the Wolox Chat widget. The end goal is to provide a means of communication between you and your users without them necessarily leaving the website to contact you. You can find the entire code for this project on this repo.

Here is a demo of what you will build

Creating a CometChat app

Go to your CometChat dashboard and create a new app called wolox-chat-widget-cometchat. After creating your new app, make sure the badge on card says v2 as the API you’ll be using is a bit different from v1 apps.

Next, click on the explore link and go to the API Keys tab. You should see an already generated API key and APP ID. Copy them as you’ll need them shortly. After copying out your keys, create a new user on the users tab. Use admin as the uid and Admin as the name. Note this down as well because you’ll need it soon enough

Building the backend

First, create a project folder called wolox-chat-widget-cometchat for your project. In this folder, create two other folders. One called backend and the other called frontend. As mentioned earlier, this app requires a Node.js backend and a React frontend. In the backend, you'll set up a REST API that will communicate with the CometChat API and serve data to the frontend.

Next, open the backend folder in your terminal and run this command:

npm init -y

This command generates a package.json file in your backend folder. Next, install dependencies for your backend by running this command:

npm install axios cors dotenv express uuid

Here is a rundown of the dependencies you just installed:

  • axios: Axios is a promised based HTTP library for making asynchronous requests. This will be used for sending and fetching data from the CometChat API.

  • cors: Because the frontend will be run on another port, it is important that you allow Cross Origin Resource Sharing to prevent any errors.

  • dotenv: This package will allow you to read environmental variables present in this project.

  • uuid: This package will allow you generate unique identifiers for newly created users.

  • express: The express package is what you'll use to setup the server and respond to requests from the frontend.

Next, you need to setup your environment variables. Create a new .env file in the backend folder and paste this snippet:

COMETCHAT_API_KEY=YOUR_COMETCHAT_API_KEY
COMETCHAT_APP_ID=YOUR_COMETCHAT_APP_ID

Replace the placeholders in this file with the corresponding credentials from the CometChat app you created earlier. Note that you should never commit your env file to version control.

Next, you need to create a script to run your application. Open your package.json file and add a scripts key for running a Node server. The scripts  JSON object already exists, so add the server property like so:

# backend/package.json

"scripts": {
 ...
 "server": "node index.js"
}

The index.js file referenced here does not exist yet, so create an index.js file in the backend folder. Inside the file, paste this snippet:

// backend/index.js
require('dotenv/config')
const express = require('express')
const uuidv4 = require('uuid/v4')
const axios = require('axios')
const cors = require('cors')

Here, you imported the dependencies you installed earlier, The dotenv package should be required at the topmost level as possible.

Next, add this snippet:

// backend/index.js
const PORT = process.env.PORT || 4000
const app = express()
app.use(cors())
const headers = {
   appid: process.env.COMETCHAT_APP_ID,
   apikey: process.env.COMETCHAT_API_KEY,
   'content-type': 'application/json',
   accept: 'application/json'
}
const adminUID = 'admin'
const baseUrl = 'https://api-eu.cometchat.io/v2.0/users'

The baseUrl depends on the region you selected. If you selected Europe, this is the correct URL. If you selected USA instead, the correct thing should be https://api-us.cometchat.io/v2.0/users.

Here, you created a dedicated port for the server to run if none is specified in the environmental variables. You also initialized an express app and added the cors middleware. Because you're going to be making multiple axios requests, the headers variables contain all the necessary headers for those requests to avoid repetition.

Lastly, the adminUID and baseUrl variables are extracted at the top. This is good in case you decide to change the admin uid or use the base URL for v1. In the next steps, you will create three endpoints to be used on the frontend.

One of such is the endpoint for creating new users when they visit the homepage of the website. Add this snippet to your index.js file:

// backend/index.js
app.get('/api/create-user', async (_, res) => {
 const randomUUID = uuidv4()
 const newUser = {
   uid: randomUUID,
   name: randomUUID
 }
 try {
   const response = await axios.post(baseUrl, JSON.stringify(newUser),
     { headers }
   )
   const uid = await response.data.data.uid
   const user = await createAuthToken(uid)
   res.status(200).json({ user })
 } catch (err) {
   console.log({ 'create-user': err })
 }
})

The primary function of this endpoint is to create new users and that is done by making a POST request to CometChat API with information about the new user, such as the UID and name. For simplicity, a random id is generated and used for both the uid and the name of the user. If this request is successful, what is returned is the UID of the user.

You then use that UID to create a token for that user to use for logging in. You'll notice that a function createAuthToken is called. Paste the function just below your global variables and before the endpoint:

// backend/index.js
async function createAuthToken(uid) {
   try {
       const response = await axios.post(`${baseUrl}/${uid}/auth_tokens`, null,
         { headers }
       )
       return response.data.data
   } catch (err) {
       console.log({ 'create-auth-token': err })
   }
}

This function takes in a UID of the user and returns JSON containing an authToken and the UID of the user. In other words, this is the final data returned after a new user is created.

Next, you will create another endpoint that will return an auth token for the admin. Paste the snippet below the first endpoint like so:

// backend/index.js
app.get('/api/authenticate-user', async (req, res) => {
   const uid = await req.query.uid
   const user = await createAuthToken(uid)
   res.status(200).json({ user })
})

In this endpoint, the UID of the admin is gotten from the query string, all you have to do now is pass it to the createAuthToken function in order to return an authentication token for the admin.

The next endpoint you will create is one that will return all the users. Add this snippet below the second endpoint:

// backend/index.js

app.get('/api/get-users', async (_, res) => {
   try {
       const response = await axios.get(baseUrl, {
           headers
       })
       const users = await response.data.data.filter(user => user.uid !== adminUID)
       res.status(200).json({ users })
   } catch (err) {
       console.log({ 'get-users': err })
   }
})

Before returning the users returned from the axios request, the admin UID is filtered to prevent returning the information about the admin since it is not necessary.

Finally, for the backend, add this snippet at the end of the file:

// backend/index.js
app.listen(PORT, () => {
   console.log(`Server listening on port ${PORT}`)
})

This snippet will take care of running the server on the port you defined earlier. To run the server all you need to do now is open your terminal and run the following:

npm run server

If everything goes well, you should now see a message in the console that reads "Server listening on port 4000".

You can test this out in the browser by visiting each of those endpoints and making sure data is returned. That concludes the backend for this app. In the next section, you'll start working on the frontend.

Building the Frontend

You will use the create-react-app package to scaffold a new React project for the frontend. Open another terminal window, make sure you're in the wolox-chat-widget-cometchat directory, then run this command:

npx create-react-app frontend

This step installs all the necessary dependencies needed to start this project. This process can take a couple of minutes to complete depending on your internet speed.

The next thing you will do is to install dependencies peculiar to the project. For this project, you need the following:

  • @cometchat-pro/chat: This package will allow you to use CometChat JavaScript SDK and all its features.

  • react-router-dom: Since this is a single page application, you need this package for client-side routing.

  • react-chat-widget: This package will be used to display a floating widget for the user to chat.

  • uuid: This package will allow you to generate unique identifiers for use with React.

To install the above dependencies, move into the frontend directory and run this command:

npm install @cometchat-pro/chat react-router-dom react-chat-widget uuid

The next step is not entirely necessary but will help avoid confusion in this tutorial. So, go ahead and delete the following files in the src directory namely, serviceWorker.js, logo.svg, index.css , App.test.js, and App.css. Deleting these files will break the app at this point but that will be fixed as you progress.

To give this app a decent look, you will add Bootstrap for styling. You can add a link to bootstrap CDN by adding this snippet in the head tag of public/index.html as follows:

<link
 rel="stylesheet"
 href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
 integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
 crossorigin="anonymous"
/>

While you're here, change the title of this app to Wolox Chat Widget CometChat.

Initializing CometChat

Next, go ahead and create a file called .env at the root of the frontend folder and add this code to it:

REACT_APP_COMETCHAT_APP_ID=YOUR_COMETCHAT_APP_ID

Replace the placeholders with the actual credentials from your CometChat dashboard. Also, note that you should not commit this file to version control.

Next, create a config.js file in the src directory and paste this snippet:

// frontend/src/config.js
const config = {
 adminUID: 'admin',
 adminName: 'Admin',
 appID: process.env.REACT_APP_COMETCHAT_APP_ID
};
export default config;

Now open src/index.js file replace everything in it with this snippet:

// frontend/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { CometChat } from '@cometchat-pro/chat';
import config from './config';
CometChat.init(config.appID).then(
 () => {
   console.log('Initialized cometchat');
 },
 () => {
   console.log('Failed to initialize cometchat');
 }
);
ReactDOM.render(<App />, document.getElementById('root'));

This will ensure that CometChat is initialized in your application.

Setting Up Application Routes

For this project, there you’re going to need three routes, one for the user and two for the admin. The  / route will load the wolox chat widget for the user to start chatting. The other two routes will pertain to the admin. The /admin route will load up the admin component containing the list of users which will navigate to /admin/:uid where individual messages can then be fetched and replies can be sent.

To begin, open src/App.js and replace the default code with this snippet:

// frontend/src/App.js

import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import User from './components/user';
import Admin from './components/admin';
function App() {
 return (
<Router>
<Route exact path='/' component={User} />
<Route exact path='/admin' component={Admin} />
<Route exact path='/admin/:uid' component={Admin} />
</Router>
 );
}
export default App;

If you've used react router before, you'd know this is a familiar setup. A Router component is used to wrap the entire app and individual Routes that map to paths in the browser. What you'll notice is that the Admin component is used to match to paths, that's because its mostly the same content they both render. In the next step, you're going to create each of these components.

Creating the User Component

As mentioned earlier, this component handles the display of the chat widget. I have also added a few extra styling to this component to make it a bit presentable, but nothing too fancy to take away the point of this tutorial.

Create a file called user.js in the src/components directory and add this snippet:

// frontend/src/components/user.js
import React, { useEffect } from 'react';
import { CometChat } from '@cometchat-pro/chat';
import { Widget, addResponseMessage, dropMessages } from 'react-chat-widget';
import 'react-chat-widget/lib/styles.css';
import { Link } from 'react-router-dom';
import config from '../config';

The CometChat module should seem familiar by now because you used it in src/index.js when you initialized the SDK. Whenever you need to handle authentication or messages, you're going to need that module. The Widget, component will be used to display a floating action button that will pop up a chat widget. The addResponseMessage and dropMessages are used for displaying messages to the user and deleting messages respectively.
You also imported the CSS file necessary for styling the widget appropriately.

The next thing you need to do is define a functional component called User and export it. Paste this snippet below the imports:

// frontend/src/components/user.js

function User() {
 // custom functions and hooks go here...

return (
<div style={{ background: '#000', height: '100vh' }}>
<header>
<div className='container py-2'>
<div>
<h1 className='text-center text-white'>ACME.</h1>
</div>
<ul
           className='nav nav-tabs'
           style={{ background: '#000', border: 'none' }}
>
<li className='nav-item'>
<Link
               style={{ color: '#fff', borderBottom: '3px solid #fff' }}
               className='nav-link'
               to='/#'
>
               Home
</Link>
</li>
<li className='nav-item'>
<Link style={{ color: '#ccc' }} className='nav-link' to='/#'>
               Features
</Link>
</li>
<li className='nav-item'>
<Link style={{ color: '#ccc' }} className='nav-link' to='/#'>
               Contact
</Link>
</li>
</ul>
</div>
</header>
<div
       className='jumbotron text-white'
       style={{
         backgroundColor: '#000'
       }}
>
<div className='container text-center'>
<h1 className='display-4'>ACME.</h1>
<p className='lead'>
           ACME is a San Francisco based design agency. We build amazing web
           experiences
</p>
<Link className='btn btn-light btn-lg' to='/' role='button'>
           Learn more
</Link>
</div>
</div>
<Widget handleNewUserMessage={handleNewUserMessage} title={`ACME Chat`} />
</div>
 );
}

export default User;

In this snippet, you created the User function and rendered the JSX. You will now add other functions and hooks to the component.

Add this snippet just before the return statement:

// frontend/src/components/user.js
useEffect(() => {
   addResponseMessage(
     'Hi, if you have any questions, please ask them here. Please note that if you refresh the page, all messages will be lost.'
   );
   const createUser = async () => {
     try {
       const userResponse = await fetch(
         'http://localhost:4000/api/create-user'
       );
       const json = await userResponse.json();
       const user = await json.user;
       await CometChat.login(user.authToken);
     } catch (err) {
       console.log({ err });
     }
   };
   createUser();
 }, []);

This useEffect hook will run as soon as the component is mounted. In this hook, you first called the addResponseMessage function imported earlier. This function will display a default message to the user. Next, the createUser function will handle the creation of a new user by making a request to the API in the backend and logging in with the authToken returned.

Now that a new user can be created and logged in, the logical step is to allow the user to chat with the admin. CometChat provides a way for this to happen by setting up a message listener on this component to handle the sending and receiving of new messages. Paste this snippet just below the first useEffect hook:

// frontend/src/components/user.js
useEffect(() => {
   const listenerId = 'client-listener-key';
   CometChat.addMessageListener(
     listenerId,
     new CometChat.MessageListener({
       onTextMessageReceived: message => {
         addResponseMessage(message.text);
       }
     })
   );
   return () => {
     CometChat.removeMessageListener(listenerId);
     CometChat.logout();
     dropMessages();
   };
 }, []);

This second useEffect function is responsible for setting up a message listener on this component. CometChat provides hooks inside the message listener to enable you only listen for functions you care about. One of such hook is the onTextMessageReceived. This hook gets triggered whenever a new text message is received. Once a new text message is received, that message will then be displayed to the user by calling the addResponseMessage function again.

The other significant part of this useEffect hook is the return function that is called when this component is unmounted. You can think of it as componentWillUnmount in terms of class components. The message listener must be removed and the user logged out to prevent a memory leak.

The last function in this component will handle what happens when the user submits the chat form. Create a new function handleNewUserMessage just below the last useEffect hook like so:

// frontend/src/components/user.js
const handleNewUserMessage = async newMessage => {
   const textMessage = new CometChat.TextMessage(
     config.adminUID,
     newMessage,
     CometChat.RECEIVER_TYPE.USER
   )
   try {
     await CometChat.sendMessage(textMessage);
   } catch (error) {
     console.log('Message sending failed with error:', error);
   }
 };

This function is hooked up to the widget component and will be called anytime the user submits the form. In this function, the message typed by the user is passed as an argument and a new text message object is created containing metadata about the type of message it is and who the message is for. In this case, the message is for the admin. Finally, the message is sent by calling the sendMessage function provided by CometChat.

If there are no errors, the messages sent will be received by the admin. In the next section, you're going to set up the admin component.

Creating the Admin Component

Create a file called admin.js in src/components directory and paste this in the file:

// src/components/admin.js
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { CometChat } from '@cometchat-pro/chat';
import config from '../config';
import uuid from 'uuid';

Here, you are importing the dependencies you will need for this component. Now define a functional component called Admin in this file by adding this snippet:

// src/components/admin.js
function Admin({
 match: {
   params: { uid }
 }
}) {
 // state variables
 // custom functions and useEffects
 // return statement
}
export default Admin;

So far, the only thing happening here is that the UID is destructured from this component. Initially, this value will return undefined because the /admin route does not expect any parameters containing a UID. It is only when we visit /admin/:uid that the UID will have a valid value.

Also, you could have easily just written props in the function declaration instead of destructuring the UID and referenced by writing props.match.params.uid. But, I did it this way because the UID is used in multiple places and as such will seem repetitive by writing out the entire props hierarchy.

Next, you need to define some state variables for this component. Paste this snippet inside the Admin functional component:

// frontend/src/componsnets/admin.js
const [users, setUsers] = useState([]);
const [messages, setMessages] = useState([]);
const [message, setMessage] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false);

Here is a brief overview of the state variables you have declared:

  • users: Holds the list of all the users. The initial value is an empty array.

  • messages: Holds all the messages sent and received in the entire app. It is initially an empty array too.

  • message: Holds the value of the text input in the admin chat form. The initial value is an empty string.

  • isLoggedIn: Holds a truthy or false value of whether the admin has successfully logged in. The default value of this state is false.

I only explained the initial values destructured from each useState hook. The second destructured values are state updater functions that will be used to update the initial values. Learn more about React hooks here.

Now that you have set up the state variables. You need to also set up some useEffect hooks that will be called on different instances. From here on out, all the useEffect hooks will go under the state variables you just declared.

Add the first useEffect hook under the state variables like so:

// frontend/src/components/admin.js
 useEffect(() => {
   const createAuthToken = async () => {
     try {
       const response = await fetch(
         `http://localhost:4000/api/authenticate-user?uid=${config.adminUID}`
       );
       const json = await response.json();
       const admin = await json.user;
       await CometChat.login(admin.authToken);
       setIsLoggedIn(true);
     } catch (err) {
       console.log({ err });
     }
   };
   createAuthToken();
 }, []);

Much like what you did in the user component, this effect runs when this component mounts and requests from the backend an authentication token that will be used to log in the admin. When this is successful, the isLoggedIn state variable is updated to true so that now this component is aware that the admin has logged. The next useEffect hook following this one will make it clearer why this is important.

Define another useEffect hook below this one like so:

// frontend/src/components/admin.js
useEffect(() => {
   const getUsers = async () => {
     const response = await fetch('http://localhost:4000/api/get-users');
     const json = await response.json();
     const users = await json.users;
     setUsers([...users]);
   };
   getUsers();
 }, [isLoggedIn]);

In this snippet, you will notice that the dependency array contains isLoggedIn variable. What this means is that this useEffect hook will run when the component first mounts and when the value of isLoggedIn changes. On these two occasions, the getUsers function will be called to updates the users state variable.

Simple enough right? The next useEffect function will handle setting up of a message listener on this component. It is just like what you did earlier in the User component. Whenever a new message is sent or received, the function within this effect will run. Paste this code snippet below the last useEffect function:

// frontend/src/components/admin.js
useEffect(() => {
   const listenerId = 'message-listener-id';
   const listenForNewMessages = () => {
     CometChat.addMessageListener(
       listenerId,
       new CometChat.MessageListener({
         onTextMessageReceived: msg => {
           setMessages(prevMessages => [...prevMessages, msg]);
         }
       })
     );
   };
   listenForNewMessages();
   return () => {
     CometChat.removeMessageListener(listenerId);
     CometChat.logout();
   };
 }, []);

Just like it was in the user component. This useEffect hooks gets called when the component mounts.

When a new message is received, the onTextMessageReceived function present in the message listener will capture it and update the messages state variable with the new message. When this component unmounts, the return function is called which in turn removes the message listener and logs out the admin.

You could have easily put this functionality into the first useEffect hook and it would still work but, this is to avoid clutter and keep the code readable. Besides, there's no limit to the number of useEffect hooks that can live in a given component.

You will add another useEffect hook. This time, to check when users come online or go offline. Paste this snippet below the previous useEffect:

// frontend/src/components/admin.js
useEffect(() => {
   const listenerID = 'user-listener-id';
   CometChat.addUserListener(
     listenerID,
     new CometChat.UserListener({
       onUserOnline: onlineUser => {
         const otherUsers = users.filter(u => u.uid !== onlineUser.uid);
         setUsers([onlineUser, ...otherUsers]);
       },
       onUserOffline: offlineUser => {
         const targetUser = users.find(u => u.uid === offlineUser.uid);
         if (targetUser && targetUser.uid === offlineUser.uid) {
           const otherUsers = users.filter(u => u.uid !== offlineUser.uid);
           setUsers([...otherUsers, offlineUser]);
           const messagesToKeep = messages.filter(
             m =>
               m.receiver !== uid &&
               m.sender.uid !== config.adminUID &&
               m.receiver !== config.adminUID &&
               m.sender.uid !== uid
           );
           setMessages(messagesToKeep);
         }
       }
     })
   );
   return () => CometChat.removeUserListener(listenerID);
 }, [users, messages, uid]);

The listener listens to know when a user comes online or goes offline. What happens here is that users that are online are placed at the top of the list. When a user goes offline, that user is pushed to the bottom of the users list.

In the dependency array, you'll notice that there are several dependencies, namely: users, messages, and UID. This is because this effect is in sync with those state variables. Therefore, whenever those state variables change, React will re-render this component.

Now, you're going to handle a case where a patricular user is selected from the users list. In the beginning, when you created this admin component, you destructed the UID from props. Now, you're going to use that UID to fetch previous messages for a particular user. Paste this snippet under the previous useEffect function:

// frontend/src/components/admin.js
useEffect(() => {
   const fetchPreviousMessages = async () => {
     try {
       const messagesRequest = new CometChat.MessagesRequestBuilder()
         .setUID(uid)
         .setLimit(50)
         .build();
       const previousMessages = await messagesRequest.fetchPrevious();
       setMessages([...previousMessages]);
     } catch (err) {
       console.log('Message fetching failed with error:', err);
     }
   };
   if (uid !== undefined) fetchPreviousMessages();
 }, [uid]);

This useEffect hook is designed to only run when a user is selected from the users list. Basically, when the URL in the address matches something of this nature: /admin/:uid, where :uid represents a user identity. That's why there is a condition to only fetch previous messages if the UID is present.

In the body of the useEffect hook, a message request containing that UID is sent to retrieve all messages with that user and then used to update the messages state variable. Next, you will create a function to enable the admin send messages. The function will be attached to the onSubmit event handler in the JSX portion of this component.

Add the following code snippet under the last useEffect hook:

// frontend/src/components/admin.js
const handleSendMessage = async e => {
   e.preventDefault();
   const _message = message;
   setMessage('');
   const textMessage = new CometChat.TextMessage(
     uid,
     _message,
     CometChat.RECEIVER_TYPE.USER
   )
   try {
     const msg = await CometChat.sendMessage(textMessage);
     setMessages([...messages, msg]);
   } catch (error) {
     console.log('Message sending failed with error:', error);
   }
 };

When the admin submits the form, the message typed is used to construct a text message and then sent by calling the sendMessage function. If the message is sent successfully, a new message object is returned and then used to update the messages state variable.

Now, you will add the JSX to your component. Add this snippet below the handleSendMessage function:

// frontend/src/component/admin.js

return (
<div style={{ height: '100vh' }}>
<header
       className='bg-secondary text-white d-flex align-items-center'
       style={{ height: '50px' }}
>
<h3 className='px-3'>
<Link to='/admin' className='text-white'>
           Dashboard
</Link>
</h3>
       {uid !== undefined && (
<span>
           {' - '}
           {uid}
</span>
       )}
</header>
<div style={{ height: 'calc(100vh - 50px)' }}>
<div className='d-flex' style={{ height: '100%' }}>
<aside
           className='bg-light p-3'
           style={{ width: '30%', height: '100%', overflowY: 'scroll' }}
>
<h2 className='pl-4'>Users</h2>
           {users.length > 0 ? (
             users.map(user => (
<li
                 style={{
                   background: 'transparent',
                   border: 0,
                   borderBottom: '1px solid #ccc'
                 }}
                 className='list-group-item'
                 key={user.uid}
>
<Link className='lead' to={`/admin/${user.uid}`}>
                   {user.name}
</Link>
</li>
             ))
           ) : (
<span className='text-center pl-4 mt-4'>Fetching users</span>
           )}
</aside>
<main
           className='p-3 d-flex flex-column'
           style={{
             flex: '1',
             height: 'calc(100vh - 60px)',
             position: 'relative'
           }}
>
<div className='chat-box' style={{ flex: '1', height: '70vh' }}>
             {uid === undefined && !messages.length && (
<div>
<h3 className='text-dark'>Chats</h3>
<p className='lead'>Select a chat to load the messages</p>
</div>
             )}
             {messages.length > 0 ? (
<ul
                 className='list-group px-3'
                 style={{ height: '100%', overflowY: 'scroll' }}
>
                 {messages.map(m => (
<li
                     className='list-group-item mb-2 px-0'
                     key={uuid()}
                     style={{
                       border: 0,
                       background: 'transparent',
                       textAlign: m.sender.uid === uid ? 'left' : 'right'
                     }}
>
<span
                       className='py-2 px-3'
                       style={{
                         background:
                           m.sender.uid === uid ? '#F4F7F9' : '#A3EAF7',
                         borderRadius: '4px'
                       }}
>
                       {m.text}
</span>
</li>
                 ))}
</ul>
             ) : (
<p className='lead'>No messages</p>
             )}
</div>
           {uid !== undefined && (
<div
               className='chat-form'
               style={{
                 height: '50px'
               }}
>
<form
                 className='w-100 d-flex justify-content-between align-items-center'
                 onSubmit={e => handleSendMessage(e)}
>
<div className='form-group w-100'>
<input
                     type='text'
                     className='form-control mt-3'
                     placeholder='Type to send message'
                     value={message}
                     onChange={e => setMessage(e.target.value)}
                   />
</div>
<button className='btn btn-secondary' type='submit'>
                   Send
</button>
</form>
</div>
           )}
</main>
</div>
</div>
</div>
 );

From this snippet, the Admin component consists of a two column layout, what you usually refer to as a sidebar and a content area. On the sidebar is where the users are mapped over and displayed as list items containing links to each UID of the user. On the content area, a default text is shown directing the admin to select a user from the list to start chatting.

When a user is selected, this component re-renders and displays the chat form for the admin to start an interaction. In the message container, only messages exchanged between the admin and the user is displayed when any user is selected, if there are no messages, nothing is displayed.

Finally, in the chat form, the handleSendMessage, and setMessage functions are hooked up to be called when the admin submits the form and when the admin types in the input respectively.

At this point, you are done with your app! Since you already have your Node server running, you just need to run the frontend part of your application. Navigate to the frontend folder in your terminal and run this command:

npm run start

Then go to localhost:3000 to start testing your application.

Conclusion

In this article, you built a chat app using the Wolox chat widget, Bootstrap, and Node.js. You learned how to utilize some CometChat functionalities such as creating new users, authenticating them, sending and receiving messages in realtime, etc. There is a ton of things you can still do with CometChat. For example, you can enable push notifications or add end-to-end encryption to messages in your app to provide an added layer of security. Learn more about CometChat JavaScript SDK here.

Idorenyin Obong

CometChat

Idorenyin, React developer from Nigeria 🇳🇬Follow me on Twitter @kingidee

Share it with everyone!

Try out CometChat in action

Experience CometChat's messaging with this interactive demo built with CometChat's UI kits and SDKs.