We're hiring
Products

Channels

Beams

Chatkit

DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

Add web notifications to your React chat app

  • Ayooluwa Isaiah
February 5th, 2019
You will need Node 6+ installed on your machine. Prior experience with React and Node will be helpful.

In this tutorial, I’ll show you exactly how you can show notifications to the user when a new message is sent to a chatroom. The complete code used in this article can be found in this GitHub repository.

A common feature in most real-world chat applications is the ability to get notifications when a new message is received. You will be pleased to know that adding this feature to your application is trivial with Chatkit.

Prerequisites

Before you proceed with this tutorial, make sure you have Node.js (version 6 and above) and npm installed on your machine. You can find installation instructions here. Also note that prior experience with building React and Node applications is required to be able to understand how the chat app works.

Sign up for Chatkit

Open this link in a new browser tab and sign up for a free Chatkit account. Once your account is created, you need to create a new Chatkit instance for your application. Give your instance a name and hit the purple CREATE button as shown below:

As soon as your Chatkit instance is created, you will be redirected to the dashboard for the instance. Locate the Credentials tab and take note of the Instance Locator and Secret Key as we’ll be using them on the server and in the application code.

Move to the Console tab and create a new user and a new room. You can follow the instructions on this page to learn how to do so. Take note of the room ID as we’ll be using it later.

Set up a basic Node server

Launch the terminal program on your computer and create a new directory for this project with the following command:

    mkdir react-chat

Next, cd into the react-chat directory and run npm init -y to initialize your project with a package.json file. Then run the command below from within the react-chat directory to install all the dependencies we’ll be using for the server component of our application:

    npm install express body-parser cors dotenv @pusher/chatkit-server -S

Once all the dependencies have been installed, create a new file variables.env in the root of your project directory and populate it with the credentials from your Chatkit dashboard:

    // variables.env

    PORT=5200
    CHATKIT_INSTANCE_LOCATOR=<your chatkit instance locator>
    CHATKIT_SECRET_KEY=<your chatkit secret key>

Next, create another file called server.js and add the following code into it:

    // server.js

    require('dotenv').config({ path: 'variables.env' });

    const express = require('express');
    const bodyParser = require('body-parser');
    const cors = require('cors');
    const Chatkit = require('@pusher/chatkit-server');

    const app = express();

    const chatkit = new Chatkit.default({
      instanceLocator: process.env.CHATKIT_INSTANCE_LOCATOR,
      key: process.env.CHATKIT_SECRET_KEY,
    });

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/users', (req, res) => {
      const { username } = req.body;

      chatkit
        .createUser({
          id: username,
          name: username,
        })
        .then(() => {
          res.sendStatus(201);
        })
        .catch(err => {
          if (err.error === 'services/chatkit/user_already_exists') {
            res.sendStatus(200);
          } else {
            res.status(err.status).json(err);
          }
        });
    });

    app.post('/authenticate', (req, res) => {
      const authData = chatkit.authenticate({
        userId: req.query.user_id,
      });
      res.status(authData.status).send(authData.body);
    });

    app.set('port', process.env.PORT || 5200);
    const server = app.listen(app.get('port'), () => {
      console.log(`Express running → PORT ${server.address().port}`);
    });

As you can see, we have two endpoints on our basic Node server. The /users endpoint takes a username and creates a new Chatkit user on our Chatkit instance. The /authenticate endpoint serves to authenticate users who try to connect to our Chatkit instance. Because this is just a demo application, we are not doing any authentication here. We are simply returning an authentication token from chatkit.authenticate no matter what.

That’s all we need to do on the server. You can run node server.js from within your project directory to start the server. You should see a message indicating that the server is set up successfully on port 5200.

Build the React application

We need to install create-react-app in the next step so that we can use it to bootstrap a new React application. Launch a new terminal instance and run the command below to install create-react-app globally on your machine:

    npm install -g create-react-app

From the root of the react-chat directory, execute the following command to install all the dependencies that we need to build a React application:

    create-react-app client

Once the command finishes running, cd into the new client directory and run npm start to start the development server. The app should be available at http://localhost:3000.

Before we start writing the code for the React app, we need to install the axios library for making HTTP requests, the prop-types library for checking and validating React component props and the Chatkit client SDK for interacting with our Chatkit instance. Open a new terminal instance and run the command below from within the react-chat/client directory:

    npm install axios @pusher/chatkit-client prop-types  -S

Let’s go ahead and start building our app frontend.

Add the styles for the app

Before we write the application logic, let’s add some basic styles to the app. Download the Skeleton CSS boilerplate, extract the zip file and copy normalize.css and skeleton.css from the css folder to your client/src directory.

Next, open up client/src/App.css in your text editor and change its contents to look like this:

    // client/src/App.css

    .App {
      width: 100%;
      margin: 0 auto;
      display: flex;
      border: 1px solid #ccc;
      height: 100vh;
    }

    h3 {
      margin-bottom: 0;
    }

    ul {
      list-style: none;
    }

    .sidebar {
      flex-basis: 30%;
      background-color: #11D771;
      color: #333;
      padding: 5px 10px;
    }

    .sidebar section {
      margin-bottom: 20px;
    }

    .sidebar h3 {
      margin-bottom: 10px;
    }

    .user-list li {
      margin-bottom: 10px;
      font-size: 16px;
      display: flex;
      align-items: center;
    }

    .presence {
      display: inline-block;
      width: 20px;
      height: 20px;
      background-color: #fff;
      margin-right: 10px;
      border-radius: 50%;
    }

    .presence.online {
      background-color: green;
    }

    .chat-window {
      flex-grow: 1;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      overflow: auto;
    }

    .chat-window > * {
      padding: 10px 20px;
    }

    .chat-header, .chat-footer {
      display: flex;
      align-items: center;
    }

    .chat-header {
      border-bottom: 1px solid #ccc;
    }

    .chat-session {
      flex-grow: 1;
      display: flex;
    }

    .message-list {
      flex-grow: 1;
      display: flex;
      flex-direction: column;
    }

    .user-message {
      margin-top: 10px;
    }

    .user-message span {
      display: block;

    }
    .user-id {
      font-weight: bold;
      margin-bottom: 3px;
    }

    .chat-footer {
      border-top: 1px solid #ccc;
    }

    .chat-footer form, .chat-footer input {
      width: 100%;
    }

    .message-form {
      display: flex;
      margin-bottom: 0;
    }

    .message-form input {
      margin-right: 20px;
      margin-bottom: 0;
    }

    input[type="text"] {
      color: #333;
    }

Application logic

Open up client/src/App.js and change its contents to look like this:

    // client/src/App.js

    import React, { Component } from 'react';
    import axios from 'axios';
    import Chatkit from '@pusher/chatkit-client';
    import Chat from './Chat';

    import './normalize.css';
    import './skeleton.css';
    import './App.css';

    class App extends Component {
      state = {
        username: '',
        userInput: '',
        messages: [],
        currentUser: null,
        users: [],
      };

      updateInput = event => {
        const { name, value } = event.target;

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

      sendMessage = event => {
        event.preventDefault();

        const { userInput, currentUser } = this.state;
        const messageObj = {
          text: userInput,
          roomId: '<your room id>',
        };

        currentUser.sendMessage(messageObj);

        this.setState({
          userInput: '',
        });
      };

      addUser = event => {
        event.preventDefault();
        const { username } = this.state;

        axios
          .post('http://localhost:5200/users', { username })
          .then(() => {
            const tokenProvider = new Chatkit.TokenProvider({
              url: 'http://localhost:5200/authenticate',
            });

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

            return chatManager.connect().then(currentUser => {
              currentUser.subscribeToRoom({
                roomId: '<your room id>',
                messageLimit: 0,
                hooks: {
                  onMessage: message => {
                    const { messages } = this.state;
                    messages.push(message);
                    this.setState(
                      {
                        messages,
                      });
                  },
                  onPresenceChanged: (state, user) => {
                    const users = currentUser.users.sort((a, b) => {
                      if (a.presence.state === 'online') return -1;

                      return 1;
                    });

                    this.setState({
                      users,
                    });
                  },
                },
              });

              this.setState(
                {
                  currentUser,
                  users: currentUser.users,
                });
            });
          })
          .catch(error => console.error(error));
      };

      render() {
        const { username, users, currentUser, userInput, messages } = this.state;

        return (
          <div className="container">
            <Chat
              users={users}
              username={username}
              userInput={userInput}
              messages={messages}
              currentUser={currentUser}
              updateInput={this.updateInput}
              addUser={this.addUser}
              sendMessage={this.sendMessage}
            />
          </div>
        );
      }
    }

    export default App;

Don’t forget to update <your chatkit instance locator> and <your room id> with the appropriate details from your Chatkit instance dashboard. Then create a new Chat.js file in the client/src directory and change it to look like this:

    // client/src/Chat.js

    import React from 'react';
    import PropTypes from 'prop-types';

    const Chat = props => {
      const {
        username,
        updateInput,
        users,
        addUser,
        currentUser,
        userInput,
        messages,
        sendMessage,
      } = props;

      return (
        <div className="App">
          <aside className="sidebar">
            {!currentUser ? (
              <section className="join-chat">
                <h3>Join Chat</h3>
                <form onSubmit={addUser}>
                  <input
                    placeholder="Enter your username"
                    type="text"
                    name="username"
                    value={username}
                    onChange={updateInput}
                  />
                </form>
              </section>
            ) : null}

            <section className="online-members">
              <h3>Room Users</h3>
              <ul className="user-list">
                {users.map((user, index) => (
                  <li key={`${user}-${index}`}>
                    <span className={`presence ${user.presence.state}`} />
                    <span>{user.name}</span>
                  </li>
                ))}
              </ul>
            </section>
          </aside>

          <main className="chat-window">
            <header className="chat-header">
              <h3>Chat</h3>
              <span className="participants" />
            </header>
            <section className="chat-session">
              <ul className="message-list">
                {messages.map((message, index) => (
                  <li key={index} className="user-message">
                    <span className="user-id">{message.senderId}</span>
                    <span>{message.text}</span>
                  </li>
                ))}
              </ul>
            </section>
            <footer className="chat-footer">
              <form className="message-form" onSubmit={sendMessage}>
                <input
                  placeholder="Type a message. Hit Enter to send"
                  type="text"
                  value={userInput}
                  name="userInput"
                  onChange={updateInput}
                />
                <button type="submit">Send</button>
              </form>
            </footer>
          </main>
        </div>
      );
    };

    Chat.propTypes = {
      username: PropTypes.string.isRequired,
      updateInput: PropTypes.func.isRequired,
      users: PropTypes.array.isRequired,
      addUser: PropTypes.func.isRequired,
      userInput: PropTypes.string.isRequired,
      messages: PropTypes.array.isRequired,
      sendMessage: PropTypes.func.isRequired,
      currentUser: PropTypes.object,
    };

    export default Chat;

The application layout is contained in the Chat.js file. As you can see, it looks like a typical chat app with a list of users on the left, and the main chat screen on the right. The username field under Join Chat takes the username of the user and calls the addUser() method in App.js when the form is submitted.

In addUser, we send a POST request to the /users route defined in our Node server. If the request is successful, we connect to our Chatkit instance and add the user to the chatroom using the subscribeToRoom() method, otherwise an error is logged to the console.

In the hooks object, the onMessage event is triggered when a new message is sent to the chatroom. We simply append the new message to the messages array so that it is displayed in the chat window. Similarly, when a user joins or leaves the chatroom, the onPresenceChanged hook runs so that we can update the online status for the user on the sidebar.

Finally, when a message is sent by submitting the form at the bottom of the chat window, we pass the message content and room id to Chatkit in the sendMessage() method and then clear the text input by setting userInput to an empty string.

Show notifications on new messages

Let’s make it possible for the application to show a new notification whenever a new message is sent to the chatroom. Update client/src/App.js to look like this:

    // client/src/App.js

    import React, { Component } from 'react';
    import axios from 'axios';
    import Chatkit from '@pusher/chatkit-client';
    import Chat from './Chat';

    import './normalize.css';
    import './skeleton.css';
    import './App.css';

    class App extends Component {
      state = {
        username: '',
        userInput: '',
        messages: [],
        currentUser: null,
        users: [],
      };

      updateInput = event => {
        const { name, value } = event.target;

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

      grantNotificationPermission = () => {
        if (!('Notification' in window)) {
          alert('This browser does not support system notifications');
          return;
        }

        if (Notification.permission === 'granted') {
          new Notification('You are already subscribed to message notifications');
          return;
        }

        if (
          Notification.permission !== 'denied' ||
          Notification.permission === 'default'
        ) {
          Notification.requestPermission().then(result => {
            if (result === 'granted') {
              new Notification(
                'Awesome! You will start receiving notifications shortly'
              );
            }
          });
        }
      };

      sendMessage = event => {
        event.preventDefault();

        const { userInput, currentUser } = this.state;
        const messageObj = {
          text: userInput,
          roomId: '<your room id>',
        };

        currentUser.sendMessage(messageObj);

        this.setState({
          userInput: '',
        });
      };

      showNotification = message => {
        const { username } = this.state;
        if (message.senderId !== username) {
          const title = message.senderId;
          const body = message.text;

          new Notification(title, { body });
        }
      };

      addUser = event => {
        event.preventDefault();
        const { username } = this.state;

        axios
          .post('http://localhost:5200/users', { username })
          .then(() => {
            const tokenProvider = new Chatkit.TokenProvider({
              url: 'http://localhost:5200/authenticate',
            });

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

            return chatManager.connect().then(currentUser => {
              currentUser.subscribeToRoom({
                roomId: '<your room id>',
                messageLimit: 0,
                hooks: {
                  onMessage: message => {
                    const { messages } = this.state;
                    messages.push(message);
                    console.log(messages);
                    this.setState(
                      {
                        messages,
                      },
                      () => this.showNotification(message)
                    );
                  },
                  onPresenceChanged: (state, user) => {
                    const users = currentUser.users.sort((a, b) => {
                      if (a.presence.state === 'online') return -1;

                      return 1;
                    });

                    this.setState({
                      users,
                    });
                  },
                },
              });

              this.setState(
                {
                  currentUser,
                  users: currentUser.users,
                },
                () => this.grantNotificationPermission()
              );
            });
          })
          .catch(error => console.error(error));
      };

      render() {
        const { username, users, currentUser, userInput, messages } = this.state;

        return (
          <div className="container">
            <Chat
              users={users}
              username={username}
              userInput={userInput}
              messages={messages}
              currentUser={currentUser}
              updateInput={this.updateInput}
              addUser={this.addUser}
              sendMessage={this.sendMessage}
            />
          </div>
        );
      }
    }

    export default App;

The grantNotificationPermission() method is invoked when a user is added to the room. This prompts the user to enable notifications in the browser. As long as this permission is granted, a new notification will be displayed to the user as specified in the showNotification() method.

Don’t forget to update <your chatkit instance locator> and <your room id> as before.

Wrap up

In this tutorial, I’ve demonstrated how to add web notifications to your Chatkit powered application. All the code written in this tutorial is available on GitHub for you to check out and run locally.

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

Products

  • Channels
  • Beams
  • Chatkit

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