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 emoji support to a React chatroom

  • Ayooluwa Isaiah
March 22nd, 2019
You will need Node 8+ installed on your machine.

In this tutorial, we’ll add emoji support to a chatroom built using React and Chatkit. We’ll explore two common ways of using emojis: through an emoji picker, and using the : shortcut to quickly search for and select and emoji.

Here's a live demo of what we’ll be building:

This article builds upon what was covered in the tutorial on sending direct messages with Chatkit, so you need to go over that one first before moving on to this one. You can clone this GitHub repository and follow the instructions in the README file to get set up.

Prerequisites

You need to have Node.js (version 8 or later) installed on your machine to be able to follow through with this tutorial. You can check out this page for instructions on how to upgrade your Node installation.

Install additional dependencies

We need a few additional dependencies to build this project. react-textarea-autocomplete provides the ability to autocomplete emojis in the text input, when emoji-mart provides a beautiful emoji picker that we can take advantage of. It looks and works almost exactly like the one in Slack in case you’re wondering. Finally, we’re adding the react-feather package for icons.

Install them all by navigating to the client directory, and running the command below:

    npm install @webscopeio/react-textarea-autocomplete react-feather emoji-mart --save

Update the styles for the application

Go ahead and update the styles for the app in App.css as shown below. This is to account for the new features and components that have been added to the application.

    // client/src/App.css

    html {
      box-sizing: border-box;
    }

    *, *::before, *::after {
      box-sizing: inherit;
      margin: 0;
      padding: 0;
    }

    ul {
      list-style: none;
    }

    h4 {
      padding-left: 20px;
      margin-bottom: 10px;
    }

    .App {
      width: 100vw;
      height: 100vh;
      display: grid;
      grid-template-columns: 1fr 4fr 1fr;
    }

    .right-sidebar {
      border-left: 1px solid #ccc;
    }

    .left-sidebar {
      border-right: 1px solid #ccc;
    }

    .user-profile {
      height: 70px;
      display: flex;
      align-items: flex-start;
      padding-right: 20px;
      padding-left: 20px;
      justify-content: center;
      flex-direction: column;
      border-bottom: 1px solid #ccc;
    }

    .user-profile span {
      display: block;
    }

    .user-profile .username {
      font-size: 20px;
      font-weight: 700;
    }

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

    .room-member {
      justify-content: space-between;
      padding: 0 20px;
      height: 60px;
    }

    .send-dm {
      opacity: 0;
      pointer-events: none;
      font-size: 20px;
      border: 1px solid #eee;
      border-radius: 5px;
      margin-bottom: 0;
      padding: 0 10px;
      line-height: 1.4;
      height: auto;
    }

    .room-member:hover .send-dm {
      opacity: 1;
      pointer-events: all;
    }

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

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

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

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

    .room-icon {
      display: inline-block;
      margin-right: 10px;
    }

    .chat-screen {
      display: flex;
      flex-direction: column;
      height: 100vh;
    }

    .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: auto;
      display: flex;
      flex-direction: column;
      justify-content: flex-end;
      margin-bottom: 0;
      min-height: min-content;
      position: relative;
    }

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

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

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

    .message-form {
      border-top: 1px solid #ccc;
      width: 100%;
      display: flex;
      align-items: center;
    }

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

    .rta {
      flex-grow: 1;
    }

    .emoji-mart {
      position: absolute;
      bottom: 20px;
      right: 10px;
    }

    input[type="text"].message-input, textarea.message-input {
      height: 50px;
      flex-grow: 1;
      line-height: 35px;
      padding-left: 20px;
      border-radius: 0;
      border-top-left-radius: 0;
      border-top-right-radius: 0;
      border-bottom-left-radius: 0;
      border-bottom-right-radius: 0;
      border: none;
      font-size: 16px;
      color: #333;
      min-height: auto;
      overflow-y: hidden;
      resize: none;
      border-left: 1px solid #ccc;
    }

    .message-input:focus {
      outline: none;
    }

    .toggle-emoji {
      border: none;
      width: 50px;
      height: auto;
      padding: 0;
      margin-bottom: 0;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .toggle-emoji svg {
      width: 28px;
      height: 28px;
    }

    /* RTA
       ========================================================================== */

    .rta {
      position: relative;
      border-left: 1px solid #ccc;
      display: flex;
      flex-direction: column;
    }

    .rta__autocomplete {
      position: absolute;
      width: 300px;
      background-color: white;
      border: 1px solid #ccc;
      border-radius: 5px;
    }

    .rta__autocomplete ul {
      list-style: none;
      text-align: left;
      margin-bottom: 0;
    }

    .rta__autocomplete li {
      margin-bottom: 5px;
      padding: 3px 20px;
      cursor: pointer;
    }

    .rta__autocomplete li:hover {
      background-color: skyblue;
    }

    /* Dialog
       ========================================================================== */

    .dialog-container {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      background-color: rgba(0, 0, 0, 0.8);
      display: flex;
      justify-content:center;
      align-items: center;
    }

    .dialog {
      width: 500px;
      background-color: white;
      display: flex;
      align-items:  center;
    }

    .dialog-form {
      width: 100%;
      margin-bottom: 0;
      padding: 20px;
    }

    .dialog-form > * {
      display: block;
    }

    .username-label {
      text-align: left;
      font-size: 16px;
    }

    .username-input {
      width: 100%;
    }

    input[type="text"]:focus {
      border-color: #5C8436;
    }

    .submit-btn {
      color: #5C8436;
      background-color: #181919;
      width: 100%;
    }

    .submit-btn:hover {
      color: #5C8436;
      background-color: #222;
    }

Add the emoji picker component

In any sort of non-trivial chat application, an emoji picker is an ubiquitous feature. So we’re going to start by adding the ability to input emojis into text using the emoji picker provided by emoji-mart.

First, let’s add a button that can be used to toggle the emoji picker. Open up App.js and change it to look like this:

    // client/src/App.js

    import React, { Component } from 'react';
    import { Smile } from 'react-feather';

    // [..]

    class App extends Component {
      constructor() {
        super();
        this.state = {
          // [..]
          showEmojiPicker: false,
        };

        // [..]
      }

      render() {
        const {
          // [..]
          showEmojiPicker,
        } = this.state;

        return (
          <div className="App">
            // [..]
            <section className="chat-screen">
              // [..]
              <footer className="chat-footer">
                <form onSubmit={this.sendMessage} className="message-form">
                  <button
                    type="button"
                    className="toggle-emoji"
                  >
                    <Smile />
                  </button>
                  <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>
            // [..]
          </div>
        );
      }
    }

    export default App;

The app should look like this now:

Next, let’s make the button functional by toggling the emoji picker component from emoji-mart on click. Update the code in App.js to look like this:

    // client/src/App.js

    import React, { Component } from 'react';
    import { Smile } from 'react-feather';
    import { Picker } from 'emoji-mart';

    import {
      // [..]
      addEmoji,
      toggleEmojiPicker,
    } from './methods';
    // [..]

    import 'emoji-mart/css/emoji-mart.css';
    // [..]

    class App extends Component {
      constructor() {
        // [..]

        this.addEmoji = addEmoji.bind(this);
        this.toggleEmojiPicker = toggleEmojiPicker.bind(this);
      }

      render() {
        const {
          // [..]
        } = this.state;

        return (
          <div className="App">
            // [..]
            <section className="chat-screen">
              // [..]
              <ul className="chat-messages">
                <ChatSession messages={messages} />
                {showEmojiPicker ? (
                  <Picker set="emojione" onSelect={this.addEmoji} />
                ) : null}
              </ul>
              <footer className="chat-footer">
                <form onSubmit={this.sendMessage} className="message-form">
                  <button
                    type="button"
                    className="toggle-emoji"
                    onClick={this.toggleEmojiPicker}
                  >
                    <Smile />
                  </button>
                  <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>
            // [..]
          </div>
        );
      }
    }

    export default App;

Then update the methods.js file with the following new functions:

    // client/src/methods.js
    import Chatkit from '@pusher/chatkit-client';
    import axios from 'axios';

    function toggleEmojiPicker() {
      this.setState({
        showEmojiPicker: !this.state.showEmojiPicker,
      });
    }

    function addEmoji(emoji) {
      const { newMessage } = this.state;
      const text = `${newMessage}${emoji.native}`;

      this.setState({
        newMessage: text,
        showEmojiPicker: false,
      });
    }

    // [..]

    export { sendMessage, handleInput, connectToRoom, connectToChatkit, sendDM, toggleEmojiPicker, addEmoji };

The toggleEmojiPicker() method shows and hides the emoji picker component, while addEmoji() updates the message input with the selected emoji. You can try it out by selecting an emoji from the emoji picker. It should work as shown below:

Autocomplete emoji in the text input

An added way to quickly input emoji in a message is by triggering autocomplete by typing :, followed by the first few letters and a menu will appear where you can select the emoji you want to use. We’ll implement this functionality in our application using react-textarea-autocomplete. Make the following changes in App.js:

    // client/src/App.js

    import React, { Component } from 'react';
    import { Picker, emojiIndex } from 'emoji-mart';
    import { Smile } from 'react-feather';
    import ReactTextareaAutocomplete from '@webscopeio/react-textarea-autocomplete';

    import {
      // [..]
      handleKeyPress,
    } from './methods';

    // [..]

    class App extends Component {
      constructor() {
        // [..]

        this.handleKeyPress = handleKeyPress.bind(this);
      }

      render() {
        // [..]

        return (
          <div className="App">
            // [..]
            <section className="chat-screen">
              // [..]
              <footer className="chat-footer">
                <form onSubmit={this.sendMessage} className="message-form">
                  <button
                    type="button"
                    className="toggle-emoji"
                    onClick={this.toggleEmojiPicker}
                  >
                    <Smile />
                  </button>
                  <ReactTextareaAutocomplete
                    className="message-input my-textarea"
                    name="newMessage"
                    value={newMessage}
                    loadingComponent={() => <span>Loading</span>}
                    onKeyPress={this.handleKeyPress}
                    onChange={this.handleInput}
                    placeholder="Compose your message and hit ENTER to send"
                    trigger={{
                      ':': {
                        dataProvider: token =>
                          emojiIndex.search(token).map(o => ({
                            colons: o.colons,
                            native: o.native,
                          })),
                        component: ({ entity: { native, colons } }) => (
                          <div>{`${colons} ${native}`}</div>
                        ),
                        output: item => `${item.native}`,
                      },
                    }}
                  />
                </form>
              </footer>
            </section>
            // [..]
          </div>
        );
      }
    }

    export default App;

Then, update methods.js with the new handleKeyPress() method and update the sendMessage() method:

    // client/src/methods.js

    import Chatkit from '@pusher/chatkit-client';
    import axios from 'axios';

    function handleKeyPress(event) {
      if (event.key === 'Enter') {
        event.preventDefault();
        this.sendMessage();
      }
    }

    // [..]

    function sendMessage() {
      const { newMessage, currentUser, currentRoom } = this.state;

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

      currentUser.sendMessage({
        text: newMessage,
        roomId: `${currentRoom.id}`,
      });

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

    export { sendMessage, handleInput, connectToRoom, connectToChatkit, sendDM, toggleEmojiPicker, addEmoji, handleKeyPress };

In ReactTextareaAutocomplete, the dataProvider is called after each keystroke to get an array of what the suggestion list should display, and component is the component for rendering the item in suggestion list. Finally, the output defines text which will be placed into text area after the user makes a selection.

Try it out by typing :, followed by the first few letters of the emoji you want to input. It should display a few suggestions and you can click on anyone of them to make your selection.

Wrap up

In this tutorial, we learned how to jazz up your React-based chatroom with emoji support. We covered how to use add an emoji picker component, and also how to trigger emoji suggestions using the : shortcut.

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

Clone the project repository
  • Chat
  • chatroom
  • JavaScript
  • Node.js
  • React
  • Social Interactions
  • Social
  • 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.