🎉 New release for Pusher Chatkit - Webhooks! Extend your in-app chat functionality

Create a whiteboard Electron app with React - Part 2: Adding the group chat

  • Wern Ancheta
April 16th, 2019
You will need Node and Yarn installed on your machine.

This is part two of the two-part series on creating a whiteboard app in React. In this part, we will add a group chat feature so the users in the room can talk about their ideas while drawing in the whiteboard.

Here’s what the final output for this part will look like:


Basic knowledge of React is required to follow this tutorial.

You’ll also need to have a Chatkit app instance.

You need to have completed part one of the series.

Adding the group chat

Before anything else, we first need to install the dependencies we’ll be using to implement the group chat functionality:

    yarn add @pusher/chatkit-client axios react-custom-scrollbars string-hash

Update Login screen

Now we’re ready to update the Login screen. Start by importing a couple of the dependencies we just installed:

    // src/screens/Login.js
    import Pusher from "pusher-js";

    // add these:
    import axios from "axios"; // for making an HTTP request to the server
    import stringHash from "string-hash"; // for getting a hash from a string value

Next, update the login function to create a hash from the username. The stringHash function returns a number which represents the username. Later on, we’ll use it as a unique ID for the users in Chatkit:

    login = () => {
      const { myUsername, channelName } = this.state;
      const myUserID = stringHash(myUsername).toString(); // create a hash for the username

      // next: update function to execute when group channel subscription succeeds

Next, we update the code to execute when the subscription to a group channel succeeds. So instead of immediately navigating to the Whiteboard screen, we first make a request to the /login endpoint of the server. As you’ll see later in the server code, this will allow us to register the users who are entering the channel so we can get the Chatkit room ID:

    this.group_channel.bind("pusher:subscription_succeeded", async () => {

      console.log("subscription to group succeeded");

      try {
        const response = await axios.post(`${BASE_URL}/login`, {
          user_id: myUserID,
          username: myUsername,
          channel: channelName

        if (response.status === 200) {
          this.props.navigation.navigate("Whiteboard", {
            roomID: response.data.room_id, // Chatkit room ID
            pusher: this.pusher,
            group_channel: this.group_channel
      } catch (e) {
        console.log("error occured logging in: ", e);

Update Whiteboard screen

Let’s proceed to update the Whiteboard screen. Start by importing the Chatkit package and the component for rendering the chat UI:

    // src/screens/Whiteboard.js
    import shortid from 'shortid';

    // add these:
    import Chatkit from '@pusher/chatkit-client';
    import ChatBox from '../components/ChatBox';


Initialize the state value for storing the messages to be rendered:

    state = {
      // <existing code here>
      tool: Tools.Pencil,
      messages: [] // add this

Update componentDidMount to get the new nav params that were passed from the Login screen earlier:

    async componentDidMount() {

      const { navigation } = this.props;
      this.myUsername = navigation.getParam("myUsername");
      this.pusher = navigation.getParam("pusher");  
      this.group_channel = navigation.getParam("group_channel");

      // add these:
      this.roomID = navigation.getParam("roomID").toString(); 
      this.myUserID = navigation.getParam("myUserID"); 
      this.channelName = navigation.getParam("channelName"); 

      // next: initialize Chatkit

Next, initialize Chatkit and subscribe to the room using the room ID returned from the server:

  myUsername: this.myUsername

try {

  // add these
  const chatManager = new ChatManager({
    instanceLocator: CHATKIT_INSTANCE_LOCATOR,
    userId: this.myUserID,
    tokenProvider: new TokenProvider({ url: CHATKIT_TOKEN_PROVIDER_ENDPOINT })

  this.currentUser = await chatManager.connect();
  await this.currentUser.subscribeToRoom({
    roomId: this.roomID, 
    hooks: {
      onMessage: this.onReceive
    messageLimit: 10
} catch (err) {
  console.log("cannot connect user to chatkit: ", err);

let textGatherer = this._gatherText();

      the rest of existing code...

Next, update the render method so it also renders the ChatBox component. This component is used for rendering the messages sent in the room as well as the form for sending new messages:

    <Col lg={3} className="Sidebar">
      <div className="tools">


        <div className="tool">


        this.currentUser &&
          messages={this.state.messages} />

Next, add the onReceive and _getMessage function. onReceive gets fired every time a new message is received from Chatkit. We simply create a new messages array by appending the new message to the end of the existing one:

    onReceive = async (data) => {
      let { message } = await this._getMessage(data);
      await this.setState(prevState => ({
        messages: [...prevState.messages, message]

The _getMessage function is used for filtering the message data so it only returns what we need:

    _getMessage = async ({ id, senderId, sender, text }) => {
      const msg_data = {
        _id: id, // unique message ID
        text: text, // message inputted by the user
        user: {
          _id: senderId, // unique user ID
          name: sender.name // username

      return {
        message: msg_data

ChatBox component

Here’s the code for the ChatBox component. This component is responsible for rendering the messages as well as the form for sending a new message:

    // src/components/ChatBox.js
    import React, { Component } from 'react';
    import { Button, Input } from 'reactstrap';
    import { Scrollbars } from 'react-custom-scrollbars';
    import MessageBox from './MessageBox';

    class ChatBox extends Component {

      state = {
        is_sending: false, // whether the sending button is disabled or not
        message: '' // the message to be sent

      render() {
        return (
          <div className="ChatBox">
              style={{ height: 250, width: 300 }}
              <div className="MessageBoxes">{this._renderMessages()}</div>

            <div className="textInputContainer">
                placeholder="Enter message here" 
                onChange={this.onUpdateMessage} />

            <div className="buttonContainer">
                {this.state.is_sending ? "Sending…" : "Send"}


      // next: add _renderMessages function


    export default ChatBox;

Here’s the function for rendering individual messages. This uses a MessageBox component which we’ll create later:

    _renderMessages = () => {
      return this.props.messages.map(msg => {
        return <MessageBox msg={msg} userID={this.props.userID} />

Next, here’s the function for updating the value of the text field for entering a new message:

    onUpdateMessage = evt => {
        message: evt.target.value

    // next: add sendMessage function

Lastly, here’s the function for sending a message with Chatkit. All it requires is the text to be sent and the room ID which was passed from the ChatBox component:

    sendMessage = async () => {
      let msg = {
        text: this.state.message,
        roomId: this.props.roomID

        is_sending: true

      try {
        await this.props.currentUser.sendMessage(msg);
          is_sending: false,
          message: ""
      } catch (err) {
        console.log("error sending message: ", err);

MessageBox component

Here’s the code for the MessageBox component. This renders the message and the username of the user who sent it:

    // src/components/MessageBox.js

    import React from 'react';

    const MessageBox = ({ msg, userID }) => {

      const className = (msg.user._id === userID) ? "MessageRow Me" : "MessageRow";

      return (
        <div className={className}>
          <div className="ChatBubble">
            <div className="username">{msg.user.name}</div>
            <div className="text">{msg.text}</div>


    export default MessageBox;

Update the styles

We used new class names in the ChatBox and MessageBox components. Here are the styles applied to them:

    // src/index.css
    .ChatBox {
      margin-top: 30px;

    .MessageRow.Me {
      float: right;
      clear: both;

    .MessageRow {
      float: left;
      clear: both;

    .ChatBubble {
      background: #d1e7ff;
      margin-bottom: 10px;
      padding: 10px;
      border-radius: 10px;
      text-align: left;
      font-size: 14px;
      vertical-align: text-bottom;
      max-width: 250px;

    .username {
      font-weight: bold;

Update the server

Now we’re ready to update the server. Start by installing the Chatkit server module:

    yarn add @pusher/chatkit-server

Next, update your server/.env file to include the Chatkit credentials:


Next, update the server file to use Chatkit:

    // server/server.js
    // <existing code..>
    const cors = require("cors");

    // add these
    const Chatkit = require("@pusher/chatkit-server");

    var channels = []; // for storing channels and usernames within them

Next, initialize Chatkit:

    // <existing code..>
    var pusher = new Pusher({
     // ...

    // add these:
    const chatkit = new Chatkit.default({
      instanceLocator: `v1:us1:${process.env.CHATKIT_INSTANCE_ID}`,
      key: process.env.CHATKIT_SECRET_KEY

Add a createUser function. This is responsible for creating Chatkit users:

    const createUser = async (user_id, username) => {
      try {
        await chatkit.createUser({
          id: user_id,
          name: username
      } catch (err) {
        if (err.error === "services/chatkit/user_already_exists") {
          console.log("user already exists: ", err);
        } else {
          console.log("error occurred: ", err);

Next, create a new endpoint called login and add the following. As you’ve seen in the src/screens/Login.js file earlier, this endpoint receives the channel name, user ID, and username of the user who is logging in. We use it to determine whether the channel name (room name) already exists or not. If it doesn’t already exist then we create a Chatkit user as well as a room:

    app.post("/login", async (req, res) => {
      const { channel, user_id, username } = req.body;

      var channel_index = channels.findIndex(c => c.name === channel);
      if (channel_index === -1) {
        console.log("channel not yet created, so creating one now...");

        await createUser(user_id, username); // create a Chatkit user

        try {
          const room = await chatkit.createRoom({
            creatorId: user_id,
            name: channel

          // push to an array so its existence can be checked when someone logs in
            id: room.id.toString(),
            name: channel,
            users: [
                id: user_id,
                name: username

          return res.json({
            room_id: room.id.toString() // return the unique room ID
        } catch (err) {
          console.log("error creating room: ", err);
      } else {
        // next: add code for when channel already exists

      return res.status(500).send("invalid user");

If the channel name already exists but the supplied username doesn’t exist, then we only create a Chatkit user and return the ID of the existing room:

    const user_index = channels[channel_index].users.findIndex(
      usr => usr.name === username
    if (user_index === -1) {
      console.log("channel created, so pushing user...");

      await createUser(user_id, username);

        id: user_id,
        name: username

      return res.json({
        room_id: channels\[channel_index\]["id"].toString()

    return res.json({
      room_id: channels[channel_index].id

Running the app

At this point, you can now run the app. Start by running the server:

    cd server
    node server.js

Then the app itself:

    cd ..
    yarn start
    yarn electron-dev


In this tutorial, we added a group chat functionality to the whiteboard app so the users have a means of communicating with each other while they draw things in the canvas.

That also wraps up this series. In this series, we created a whiteboard app with chat capabilities using React, React Sketch and Chatkit.

You can find the source code for this tutorial on its GitHub repo.

Clone the project repository
  • Chat
  • Collaboration
  • JavaScript
  • Live UX
  • Node.js
  • React
  • Social
  • Channels
  • Chatkit


  • 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.