🎉 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 send multiple attachments in a chatroom

  • Ayooluwa Isaiah

July 21st, 2019
You will need to have Node 8+ installed on your machine.

This tutorial will show you how to send multiple attachments in a chatroom using the new multipart messaging format introduced in Chatkit API v3. Here’s a demo of what we’ll be building:

We will build 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’ll add a few additional packages necessary to build this project. The react-images-upload package provides a simple component for uploading and validating client side images with preview, while react-feather packages the popular Feather icons into React components. Install them both by running the following command within the client directory:

    npm install react-feather react-images-upload --save

Update the styles for the app

Go ahead and add the following styles at the end of App.css as shown below. This is to account for the new components that will be added to the application.

    // client/src/App.css

    // [..]

    svg {
      width: 24px;
      height: 24px;
    }

    .btn {
      border: none;
      width: auto;
      padding: 0;
      height: auto;
      line-height: 1.2;
      margin-bottom: 0;
    }

    .image-picker {
      margin-right: 20px;
    }

    .chooseFileButton {
      height: auto;
    }

    .fileContainer {
      background-color: #f8f8f8;
      border-radius: 4px;
      border: 1px solid #ccc;
      box-shadow: none;
      margin-bottom: 15px;
    }

    .file-dialog {
      width: 500px;
      background-color: white;
      padding: 20px;
    }

    .file-dialog h4 {
      margin-bottom: 0;
      padding-left: 0;
    }

    .file-dialog form {
      margin-bottom: 0;
    }

    .file-dialog header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 15px;
    }

    .file-dialog input {
      width: 100%;
      margin-bottom: 15px;
      border-color: #ccc;
    }

    .image-attachment {
      width: auto;
      height: auto;
      max-width: 400px;
      margin-top: 15px;
    }

    .chat-messages {
      justify-content: flex-start;
      min-height: auto;
    }

    .message-form {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

Add an image upload dialog

In this section, we’ll create a dialog where the user can attach several images and upload them to the chatroom. To get started, create a new ImageUploadDialog.js file in client/src/components and populate it with the following code:

    // client/src/components/ImageUploadDialog.js

    import React from 'react';
    import ImageUploader from "react-images-upload";
    import { X } from 'react-feather';

    const ImageUploadDialog = (props) => {
      const { onDrop, closeImageUploadDialog, handleInput, fileUploadMessage, sendFile } = props;

      return (
        <div className="dialog-container">
          <div className="file-dialog">
            <header>
              <h4>Upload an image</h4>
              <button onClick={closeImageUploadDialog} className="btn">
                <X></X>
              </button>
            </header>
            <form onSubmit={sendFile}>
              <input placeholder="Add a message about this image" type="text" onChange={handleInput} value={fileUploadMessage} name="fileUploadMessage" />
              <ImageUploader
                withIcon={false}
                buttonText="Choose images"
                onChange={onDrop}
                imgExtension={[".jpg", ".jpeg", ".png", ".gif"]}
                maxFileSize={5242880}
                withPreview={true}
              />
              <button type="submit" className="submit-btn">Upload</button>
            </form>
          </div>
        </div>
      )
    }

    export default ImageUploadDialog;

Next, add the following methods at the end of the methods.js and export them:

    // client/src/methods.js

    function onDrop(pictureFiles) {
      this.setState({
        pictures: this.state.pictures.concat(pictureFiles)
      });
    }

    function openImageUploadDialog() {
      this.setState({
        showImageUploadDialog: true
      });
    }

    function closeImageUploadDialog() {
      this.setState({
        showImageUploadDialog: false
      });
    }

    function sendFile(event) {
      event.preventDefault();
      const { currentUser, fileUploadMessage, pictures, currentRoom } = this.state;

      if (pictures.length === 0) return;

      const parts = [];
      if (fileUploadMessage.trim() !== "") {
        parts.push({
          type: "text/plain",
          content: fileUploadMessage
        });
      }

      pictures.forEach(pic => {
        parts.push({
          file: pic
        });
      });

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

      this.setState({
        fileUploadMessage: "",
        pictures: [],
        showImageUploadDialog: false
      });
    }

    export {
      sendMessage,
      handleInput,
      connectToRoom,
      connectToChatkit,
      sendDM,
      onDrop,
      sendFile,
      openImageUploadDialog,
      closeImageUploadDialog,
    };

Finally, update App.js to look like this:

    // client/src/App.js

    import React, { Component } from "react";
    import { Image } from "react-feather";
    import {
      // [..]
      onDrop,
      openImageUploadDialog,
      closeImageUploadDialog,
      sendFile
    } from "./methods";

    // [..]

    import ImageUploadDialog from "./components/ImageUploadDialog";

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

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

        // [..]
        this.onDrop = onDrop.bind(this);
        this.openImageUploadDialog = openImageUploadDialog.bind(this);
        this.closeImageUploadDialog = closeImageUploadDialog.bind(this);
        this.sendFile = sendFile.bind(this);
      }

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

        return (
          <div className="App">
            <aside className="sidebar left-sidebar">
              // [..]
            </aside>
            <section className="chat-screen">
              <header className="chat-header">
                {currentRoom ? <h3>{roomName}</h3> : null}
              </header>
              <ul className="chat-messages">
                <ChatSession messages={messages} />
              </ul>
              <footer className="chat-footer">
                <form onSubmit={this.sendMessage} className="message-form">
                  // [..]
                  <button
                    onClick={this.openImageUploadDialog}
                    type="button"
                    className="btn image-picker"
                  >
                    <Image />
                  </button>
                </form>
              </footer>
            </section>
            <aside className="sidebar right-sidebar">
            // [..]

            {showImageUploadDialog ? (
              <ImageUploadDialog
                handleInput={this.handleInput}
                fileUploadMessage={fileUploadMessage}
                onDrop={this.onDrop}
                sendFile={this.sendFile}
                closeImageUploadDialog={this.closeImageUploadDialog}
              />
            ) : null}
          </div>
        );
      }
    }

    export default App;

All we did here is to create the ImageUploadDialog component and use the ImageUploader component from react-images-upload within it to handle the selection of images from the user’s filesystem. Then we added a button next to the message input that opens up this new component.

At this point, you should be able to toggle the ImageUploadDialog component as shown in the GIF below:

Migrate to multipart messages

Previously one could only send one required text part and one optional attachment part in a message, but Chatkit (v3) changed this by adding methods that enabled one send up to 10 parts in one message and each part may be text, attachment, or link. This makes it a lot easier to send complex messages with multiple attachments, as I’m about to show you.

Before we can take advantage of these new features, we need to migrate our app to use the multipart messaging format instead of the legacy format. This is as simple as upgrading your SDK to the latest version, and changing a few methods as well as how the messages are rendered.

Start by updating the JavaScript client SDK to the latest version by running the command below from your client directory:

    npm install @pusher/chatkit-client@latest --save

Next, open up methods.js in your editor and change subscribeToRoom to subscribeToRoomMultipart within the connectToRoom function. Also update sendMessage as shown below

    // client/src/methods.js

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

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

      const parts = [];
      if (newMessage.trim() !== "") {
        parts.push({
          type: "text/plain",
          content: newMessage
        });
      }

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

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

As you can see, we’ve replaced currentUser.sendMessage with currentUser.sendMultipartMessage. This allows us to send multiple parts in a single message, although this is more evident in the sendFile method we added earlier.

Finally, update the way messages are rendered by changing the code in ChatSession.js as shown below:

    // client/src/components/ChatSession.js

    import React from 'react';
    import Proptypes from 'prop-types';
    import { format } from 'date-fns';

    const ChatSession = props => {
      const { messages } = props;
      return messages.map(message => {
        const time = format(new Date(`${message.updatedAt}`), 'HH:mm');

        const arr = message.parts.map(p => {
          if (p.partType === "inline") {
            return (
              <span>{p.payload.content}</span>
            );
          }

          if (Date.now() > Date.parse(p.payload._expiration)) {
            p.payload._fetchNewDownloadURL();
          }

          return (
            <div className="media">
              <img className="image-attachment" src={p.payload._downloadURL} alt="attachment" />
            </div>
          );
        });

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

    ChatSession.propTypes = {
      messages: Proptypes.arrayOf(Proptypes.object).isRequired,
    };

    export default ChatSession;

Voila! Sending multiple attachments through the image upload dialog should work just fine. Try it out with two or three images. It should work similarly to the GIF below:

Wrap up

In this article, I’ve introduced you to multipart messages in Chatkit and showed you how you can use this new feature to send multiple attachments in a React chatroom.

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

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