🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

How to show unread message count with React

  • Ayooluwa Isaiah

August 15th, 2019
You will need Node.js 8+ and npm installed on your machine.

In this tutorial, we’ll go over how this feature works in Chatkit. In the end, you should have a chat app that looks and works like this:

Most chat apps present the unread count for messages in a room somewhere in the application interface. This allows the user to scan the interface and quickly see how many unread messages they have and in what rooms.

Prerequisites

This tutorial assumes prior experience with working with React and Chatkit. If this is your first attempt at using Chatkit, you should probably start here first, then come back to this tutorial at a later time.

You also need to have Node.js (version 8 or later), and npm installed on your machine. Installation instructions can be found on this page.

Sign up for Chatkit

Open this link in a new browser tab, and create a new Chatkit account or sign into your existing account. Once you are logged in, create a new Chatkit instance for this tutorial and take note of your Instance Locator and Secret Key in the Credentials tab.

Also make sure your Test Token Provider is enabled as shown below. One enabled, the endpoint from which the test token will be generated will be displayed for you to copy and paste.

Next, click the Console tab and create a new user and room for your instance. You can follow the instructions on this page to learn how to do so. Once the room has been created, take note of the room ID as we’ll be using it later on.

Create a new React app

Run the command below to install create-react-app globally on your machine, then use it to bootstrap a new react React application:

    npm install -g create-react-app
    create-react-app chatkit-unread-messages

Once the app has been created, cd into the new chatkit-unread-messages directory and install the following additional dependencies that we’ll be needing in the course of building the chat app:

    npm install @pusher/chatkit-client prop-types skeleton-css --save

You can now start your development server by running npm start then navigate to http://localhost:3000 in your browser to view the app.

Add the styles for the app

Open up src/App.css in your code editor, and change it to look like this:

    // src/App.css

    .App {
      width: 100vw;
      height: 100vh;
      display: flex;
      overflow: hidden;
    }

    .sidebar {
      height: 100%;
      width: 20%;
      background-color: blanchedalmond;
    }

    .login {
      padding: 5px 20px;
    }

    .sidebar input {
      width: 100%;
    }

    .chat-rooms .active {
      background-color: whitesmoke;
      color: #181919;
    }

    .chat-rooms li {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 15px 20px;
      font-size: 18px;
      color: #181919;
      cursor: pointer;
      border-bottom: 1px solid #eee;
      margin-bottom: 0;
    }

    .room-list h3 {
      padding-left: 20px;
      padding-right: 20px;
    }

    .room-unread {
      display: inline-block;
      width: 20px;
      height: 20px;
      line-height: 20px;
      border-radius: 50%;
      font-size: 16px;
      text-align: center;
      padding: 5px;
      background-color: greenyellow;
      color: #222;
    }

    .chat-rooms li:hover {
      background-color: #D8D1D1;
    }

    .chat-screen {
      display: flex;
      flex-direction: column;
      height: 100vh;
      width: calc(100vw - 20%);
    }

    .chat-header {
      height: 70px;
      flex-shrink: 0;
      border-bottom: 1px solid #ccc;
      padding-left: 10px;
      padding-right: 20px;
      display: flex;
      flex-direction: column;
      justify-content: center;
    }

    .chat-header h3 {
      margin-bottom: 0;
      text-align: center;
    }

    .chat-messages {
      flex-grow: 1;
      overflow-y: scroll;
      display: flex;
      flex-direction: column;
      justify-content: flex-end;
      margin-bottom: 0;
      min-height: min-content;
    }

    .message {
      padding-left: 20px;
      padding-right: 20px;
      margin-bottom: 10px;
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
    }

    .message span {
      display: block;
      text-align: left;
    }

    .message .user-id {
      font-weight: bold;
    }

    .message-form {
      border-top: 1px solid #ccc;
    }

    .message-form, .message-input {
      width: 100%;
      margin-bottom: 0;
    }

    input[type="text"].message-input {
      height: 50px;
      border: none;
      padding-left: 20px;
    }

Create a basic chat application

Let’s set up a basic chat interface so that we can add the functionality to show the unread message count. The code below should be straight-forward enough to understand if you’ve worked with Chatkit before:

    // src/App.js

    import React, { Component } from "react";
    import {
      handleInput,
      connectToChatkit,
      connectToRoom,
      sendMessage,
    } from "./methods";

    import "skeleton-css/css/normalize.css";
    import "skeleton-css/css/skeleton.css";
    import "./App.css";

    class App extends Component {
      constructor() {
        super();
        this.state = {
          userId: "",
          currentUser: null,
          currentRoom: null,
          rooms: [],
          messages: [],
          newMessage: "",
        };

        this.handleInput = handleInput.bind(this);
        this.connectToChatkit = connectToChatkit.bind(this);
        this.connectToRoom = connectToRoom.bind(this);
        this.sendMessage = sendMessage.bind(this);
      }

      render() {
        const {
          rooms,
          currentRoom,
          currentUser,
          messages,
          newMessage,
        } = this.state;

        const roomList = rooms.map(room => {
          const isRoomActive = room.id === currentRoom.id ? 'active' : '';
          return (
            <li
              className={isRoomActive}
              key={room.id}
              onClick={() => this.connectToRoom(room.id)}
            >
              <span className="room-name">{room.name}</span>
            </li>
          );
        });

        const messageList = messages.map(message => {
          const arr = message.parts.map(p => {
              return (
                <span className="message-text">{p.payload.content}</span>
              );
          });

          return (
            <li className="message" key={message.id}>
              <div>
                <span className="user-id">{message.senderId}</span>
                {arr}
              </div>
            </li>
          )
        });

        return (
          <div className="App">
            <aside className="sidebar left-sidebar">
              {!currentUser ? (
                  <div className="login">
                    <h3>Join Chat</h3>
                    <form id="login" onSubmit={this.connectToChatkit}>
                      <input
                        onChange={this.handleInput}
                        className="userId"
                        type="text"
                        name="userId"
                        placeholder="Enter your username"
                      />
                    </form>
                  </div>
                ) : null
              }
              {currentRoom ? (
                <div className="room-list">
                  <h3>Rooms</h3>
                  <ul className="chat-rooms">
                    {roomList}
                  </ul>
                </div>
                ) : null
              }
            </aside>
            {
              currentUser ? (
                <section className="chat-screen">
                  <ul className="chat-messages">
                    {messageList}
                  </ul>
                  <footer className="chat-footer">
                    <form onSubmit={this.sendMessage} className="message-form">
                      <input
                        type="text"
                        value={newMessage}
                        name="newMessage"
                        className="message-input"
                        placeholder="Type your message and hit ENTER to send"
                        onChange={this.handleInput}
                      />
                    </form>
                  </footer>
                </section>
              ) : null
            }
          </div>
        );
      }
    }

    export default App;

Create a new methods.js file within the src directory and add the following code into it:

    // src/methods.js

    import { ChatManager, TokenProvider } from "@pusher/chatkit-client";

    function handleInput(event) {
      const { value, name } = event.target;

      this.setState({
        [name]: value
      });
    }

    function connectToChatkit(event) {
      event.preventDefault();
      const { userId } = this.state;

      const tokenProvider = new TokenProvider({
        url:
          "<test token provider endpoint>"
      });

      const chatManager = new ChatManager({
        instanceLocator: "<your chatkit instance locator>",
        userId,
        tokenProvider
      });

      return chatManager
        .connect()
        .then(currentUser => {
          this.setState(
            {
              currentUser,
            },
            () => connectToRoom.call(this)
          );
        })
        .catch(console.error);
    }

    function connectToRoom(roomId = "<your chatkit room id>") {
      const { currentUser } = this.state;
      this.setState({
        messages: []
      });

      return currentUser
        .subscribeToRoomMultipart({
          roomId,
          messageLimit: 10,
          hooks: {
            onMessage: message => {
              this.setState({
                messages: [...this.state.messages, message],
              });
            },
          }
        })
        .then(currentRoom => {
          this.setState({
            currentRoom,
            rooms: currentUser.rooms,
          });
        })
        .catch(console.error);
    }

    function sendMessage(event) {
      event.preventDefault();
      const { newMessage, currentUser, currentRoom } = this.state;
      const parts = [];

      if (newMessage.trim() === "") return;

      parts.push({
        type: "text/plain",
        content: newMessage
      });

      currentUser.sendMultipartMessage({
        roomId: `${currentRoom.id}`,
        parts
      });

      this.setState({
        newMessage: "",
      });
    }

    export {
      handleInput,
      connectToRoom,
      connectToChatkit,
      sendMessage,
    }

Make sure to replace the <test token provider endpoint>, <your chatkit instance locator> and <your chatkit room id> placeholders above with the appropriate values from your Chatkit dashboard.

Open the app in a few different tabs and join the chatroom under different usernames. Send a few messages for each user. It should work just fine, similar to the GIF below:

Show the unread message count

Chatkit provides an unreadCount property that we can hook into to display the number of unread messages for the current user in a room. But how do we know when a user has read a message? This is where read cursors come in.

When a user enters a room, messages in the room are loaded into the chat interface according to the messageLimit property. At this point, it is safe to say that the user has read all the messages in that room and we can indicate this by setting a read cursor on the last message.

Change the onMessage hook in the connectToRoom function as follows:

    // src/methods.js

    function connectToRoom(roomId = "21431542") {
      const { currentUser } = this.state;
      this.setState({
        messages: []
      });

      return currentUser
        .subscribeToRoomMultipart({
          roomId,
          messageLimit: 10,
          hooks: {
            onMessage: message => {
              this.setState({
                messages: [...this.state.messages, message],
              });

              const { currentRoom } = this.state;

              if (currentRoom === null) return;

              return currentUser.setReadCursor({
                roomId: currentRoom.id,
                position: message.id,
              });
            },
          }
        })
        .then(currentRoom => {
          this.setState({
            currentRoom,
            rooms: currentUser.rooms,
          });
        })
        .catch(console.error);
    }

What this means is that, if the user leaves the room by any means (such as by going offline, or switching to another room) any messages sent after the last read cursor position would count towards the unreadCount.

We can then use the onRoomUpdated connection hook to update the room in the application state and display the unread count in the chat interface. Add the hook as shown below:

    // src/methods.js

    function connectToChatkit(event) {
      // [..]

      return chatManager
        .connect({
          onRoomUpdated: room => {
            const { rooms } = this.state;
            const index = rooms.findIndex(r => r.id === room.id);
            rooms[index] = room;
            this.setState({
              rooms,
            });
          }
        })
        .then(currentUser => {
          this.setState(
            {
              currentUser,
            },
            () => connectToRoom.call(this)
          );
        })
        .catch(console.error);
    }

Finally, we want to display a room’s unread count if the number of unread messages is greater than zero. Update the roomList variable in the render() method of our chat app as shown below:

    // src/App.js

    const roomList = rooms.map(room => {
      const isRoomActive = room.id === currentRoom.id ? 'active' : '';
      return (
        <li
          className={isRoomActive}
          key={room.id}
          onClick={() => this.connectToRoom(room.id)}
        >
          <span className="room-name">{room.name}</span>
          {room.unreadCount > 0 ? (
            <span className="room-unread">{room.unreadCount}</span>
          ): null}
        </li>
      );
    });  

You can simulate unread messages by creating a different room and switching to it as one user, then send messages to the first room as another user. It will display the unread count for any of the users as appropriate:

Wrap up

In this tutorial, you learned how add unread message count to your Chatkit app. We explored how to use read cursors to determine if a user has read a message, and how to display the number of unread messages in the application interface.

You can checkout other things Chatkit can do by viewing its extensive documentation. Don't forget to grab the complete source code in this GitHub repository.

Clone the project repository
  • React
  • JavaScript
  • CSS
  • Chat
  • Node.js
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.