🎉 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

Implementing reply to message feature in a React Native chat app

  • Wern Ancheta

August 27th, 2019
You will need Node 11+, Yarn 1.13+, React Native CLI 2+ and React Native 0.59+ installed on your machine.

In a chat app, it can be incredibly useful to have a feature for replying to specific messages. This is especially true for a group chat where there are lots of people chatting. Having such a feature will provide a context on which specific message a room member is responding to.

In this tutorial, I’ll be showing you how to implement a “reply to message” feature in a React Native chat app using Chatkit and Gifted Chat.

Prerequisites

Basic knowledge of React Native is required to follow this tutorial. Some knowledge of Chatkit is also assumed as we’re working on an existing chat app.

You’ll need a Chatkit account for this tutorial. Create one if you don’t already have an account. Once you have an account, create an app instance with the test token provider enabled (you can also use your own token provider).

The following package versions are used in this tutorial. Be sure to use them in case you encounter any issues with compiling and running the app:

  • Node 11.2.0
  • Yarn 1.13.0
  • React Native CLI 2.0.1
  • React Native 0.59.9

App overview

We will be updating an existing chat app to include the “reply to message” feature. This allows any member of the room to respond to a specific message by long-pressing it. When the message is sent, it will be rendered along with the message they responded to.

Here’s what the app will look like:

You can find the app’s source code on this GitHub repo.

Bootstrapping the app

As mentioned earlier, we’ll be working on an existing chat app. To make it easy to get the existing app up and running, I’ve created a starter branch in the GitHub repo which you can switch to. This contains the basic code for implementing chat functionality:

    git clone https://github.com/anchetaWern/RNChatkitReplyToMessage.git
    cd RNChatkitReplyToMessage
    git checkout starter
    yarn
    react-native link react-native-config
    react-native link react-native-gesture-handler
    react-native link react-native-vector-icons

The above commands installs all the dependencies and links the one’s which has a native functionality.

The app uses the following packages:

  • @pusher/chatkit-client and @pusher/chatkit-server - for implementing the chat functionality.
  • react-native-gifted-chat - for easily building the chat UI.
  • axios - for communicating with the app’s server component. This allows us to do server-side API calls such as getting a list of rooms or joining a room.
  • react-navigation - for navigating between screens.
  • react-native-config and dotenv - for loading environment variables from a .env file.
  • react-native-vector-icons - for showing icons.

Next, manually add React Native Config’s Gradle path to the android/app/build.gradle file as this can’t be done automatically through react-native link:

    apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" // 2nd line

Lastly, update the .env files with your Chatkit credentials:

    // .env
    CHATKIT_INSTANCE_LOCATOR_ID="YOUR CHATKIT INSTANCE LOCATOR ID"
    CHATKIT_SECRET_KEY="YOUR CHATKIT SECRET KEY"
    CHATKIT_TOKEN_PROVIDER_ENDPOINT="YOUR CHATKIT TOKEN PROVIDER ENDPOINT"


    // server/.env
    CHATKIT_INSTANCE_LOCATOR_ID="YOUR CHATKIT INSTANCE LOCATOR ID"
    CHATKIT_SECRET_KEY="YOUR CHATKIT SECRET KEY"

Updating the app

Now, we’re ready to update the app. Start by importing the additional components that we’re going to need. Gifted Chat’s Message component serves as a wrapper for the custom chat bubble component (ChatBubbleWithReply) that we will be creating. ReplyToFooter is the component that we will render on the footer when the user long presses on a specific message:

    // src/screens/Chat.js
    import { GiftedChat, Message } from 'react-native-gifted-chat'; // add Message

    import ReplyToFooter from '../components/ReplyToFooter';
    import ChatBubbleWithReply from '../components/ChatBubbleWithReply';

Next, update the state initialization code to include the initial values that are required for implementing the feature:

    state = {
      messages: [],
      show_load_earlier: false,

      // add these:
      show_reply_to_footer: false, // whether the ReplyToFooter component is visible or not
      reply_to: null, // the username of the user the current user is responding to
      reply_to_msg: null // the actual message that the current user is responding to
    };

Next, update the render() method so it looks like the following:

    render() {
      const { messages, show_load_earlier, is_loading } = this.state;
      return (
        <View style={styles.container}>
          {
            is_loading &&
            <ActivityIndicator size="small" color="#0000ff" />
          }
          <GiftedChat
            messages={messages}
            onSend={messages => this.onSend(messages)}
            showUserAvatar={true}
            user={{
              _id: this.user_id
            }}
            loadEarlier={show_load_earlier}
            onLoadEarlier={this.loadEarlierMessages}

            onLongPress={this.onLongPress}
            renderChatFooter={this.renderChatFooter}

            renderMessage={this.renderMessage}
          />
        </View>
      );
    }

In the above code, we added the following props to Gifted Chat. These props allows us to customize the default components and function to execute for specific events:

  • onLongPress - function to execute when a specific chat bubble is long-pressed by the user. By default, this just shows an ActionSheet which allows the user to copy the text on that specific chat bubble.
  • renderChatFooter - function for rendering the chat footer.
  • renderMessage - function for rendering a custom message.

Here’s the onLongPress() function. This will set the data to be used by the ReplyToFooter component and make it visible. Gifted Chat passes the data for the message that was long-pressed and it’s made available as the second argument. From here, we can extract the name of the user who sent the message as well as their actual message. We use those to update the state:

    onLongPress = (context, message) => {
      this.setState({
        reply_to: message.user.name,
        reply_to_msg: message.text,
        show_reply_to_footer: true, // make the ReplyToFooter component visible
      });
    }

Here’s the renderChatFooter() function. This renders the ReplyToFooter component using the data that was set from the onLongPress() function. We’ll create this component later:

    renderChatFooter = () => {
      const { show_reply_to_footer, reply_to, reply_to_msg } = this.state;
      if (show_reply_to_footer) { // only render if it's set to visible
        return (
          <ReplyToFooter
            reply_to={reply_to}
            reply_to_msg={reply_to_msg}
            closeFooter={this.closeReplyToFooter} />
        );
      }
      return null;
    }

Next, here’s the function for closing the “reply to” footer. This simply resets the state values so they aren’t sent the next time the user sends a message:

    closeReplyToFooter = () => {
      this.setState({
        show_reply_to_footer: false,
        reply_to: null,
        reply_to_msg: null
      });
    }

Next, we need to update the function for sending messages. Chatkit doesn’t actually have a native support for replying to specific messages. That’s why we need to use the tools we currently have to implement the closest thing to it. In this case, the solution I came up with is to use multi-part messages. This works by adding two inline messages if the user is replying to another message:

  • First part - this is the message the current user has sent.
  • Second part - the message the current user is replying to.

A multi-part message can also have a file or URL attachment. We won’t really be handling those in this tutorial. If the message they’re responding to has an attachment, we won’t be extracting those and save it as another part of the message. We will only focus on the text message. For more information on how to attach files, check out this tutorial: Creating a file-sharing app with React Native.

Since there’s no packet for storing the username of the user they’re responding to, we simply add it with the actual message in the content property of the message. We use the @ syntax so it can easily be extracted via Regular Expressions later on when we render:

    onSend = async ([message]) => {
      const { reply_to, reply_to_msg } = this.state;
      try {
        const message_parts = [
          { type: "text/plain", content: message.text }
        ];

        if (reply_to && reply_to_msg) { // the user is responding to another message
          message_parts.push({
            type: "text/plain",
            content: `@${reply_to} ${reply_to_msg}` // syntax: @username message
          });
        }

        await this.currentUser.sendMultipartMessage({
          roomId: this.room_id,
          parts: message_parts
        });

      } catch (send_msg_err) {
        console.log("error sending message: ", send_msg_err);
      }

      // reset the state
      this.setState({
        show_reply_to_footer: false,
        reply_to: null,
        reply_to_msg: null
      });
    }

Next, update the getMessage() function. This function is responsible for formatting the messages coming from Chatkit so that it can be easily rendered by Gifted Chat. We’re updating it so it extracts the second inline message (the message the current user has responded to) if it’s available. If it’s available, we separate the username from the actual message then include it in the original message:

    getMessage = ({ id, sender, parts, createdAt }) => {
      const text_parts = parts.filter(part => part.partType === 'inline');
      let msg_data = {
        _id: id,
        text: text_parts[0].payload.content,
        createdAt: new Date(createdAt),
        user: {
          _id: sender.id.toString(),
          name: sender.name,
          avatar: sender.avatarURL
        }
      };

      if (text_parts.length > 1) { // there's a second inline message
        // separate the username from the actual message
        const replying_to = text_parts[1].payload.content;
        const replying_to_user = replying_to.match(/@[a-zA-Z0-9]+/g);
        const reply_to = replying_to_user[0].substr(1);
        const reply_to_msg = replying_to.replace(reply_to, '').substr(1);

        // update the original message data
        Object.assign(msg_data, {
          reply_to,
          reply_to_msg
        });
      }

      return {
        message: msg_data
      };
    }

Gifted Chat only requires the _id, text, createdAt, and user properties in order to render a message. The reply_to and reply_to_msg properties are custom one’s that we’ve added, but Gifted Chat will also make them available when rendering a message as you’ll see later.

Here’s the renderMessage() function. A single message object is passed as an argument to it. This allows us to use a custom function for rendering a message bubble when the reply_to and reply_to_msg properties are available in the message:

    renderMessage = (msg) => {
      const { reply_to, reply_to_msg } = msg.currentMessage; // extract the message the current message is a response to (if any)
      const renderBubble = (reply_to && reply_to_msg) ? this.renderPreview : null; 

      let modified_msg = {
        ...msg,
        renderBubble
      };

      return <Message {...modified_msg} />
    }

Here’s the renderPreview() function. This simply renders the ChatBubbleWithReply component using the updated chat bubble props. We’ll create this component later:

    renderPreview = (bubbleProps) => {
      return (
        <ChatBubbleWithReply {...bubbleProps} />
      );
    }

ReplyToFooter component We now proceed to adding the code for the two custom components that we’re using. Let’s first go through the component which shows the username and message the user is trying to respond to. As you’ve seen earlier, this accepts three props: reply_to, reply_to_msg, and closeFooter. We’re simply extracting those props in here and making use of it. We’re using Vector Icons to render a close button icon for hiding the component:

    // src/components/ReplyToFooter.js
    import React from 'react';
    import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
    import Icon from 'react-native-vector-icons/FontAwesome';

    const ReplyToFooter = ({ reply_to, reply_to_msg, closeFooter }) => {
      return (
        <View style={styles.reply_to_footer}>
          <View style={styles.reply_to_border}></View>
          <View style={styles.reply_to_container}>
              <Text style={styles.reply_to_text}>Reply to {reply_to}:</Text>
              <Text style={styles.reply_to_msg_text}>{reply_to_msg}</Text>
          </View>
          <View style={styles.close_button_container}>
            <TouchableOpacity onPress={closeFooter}>
              <Icon name="close" size={13} color="#4e4e4e" />
            </TouchableOpacity>
          </View>
        </View>
      );
    }

Next, add the styles and export the component:

    const styles = StyleSheet.create({
      reply_to_footer: {
        height: 50,
        flexDirection: 'row'
      },
      reply_to_border: {
        height: 50,
        width: 5,
        backgroundColor: '#0078ff'
      },
      reply_to_container: {
        flexDirection: 'column'
      },
      reply_to_text: {
        color: '#0078ff',
        paddingLeft: 10,
        paddingTop: 5
      },
      reply_to_msg_text: {
        color: 'gray',
        paddingLeft: 10,
        paddingTop: 5
      },
      close_button_container: {
        flex: 1,
        justifyContent: 'center',
        alignItems:'flex-end',
        paddingRight: 10
      }
    });

    export default ReplyToFooter;

ChatBubbleWithReply component Next is the custom component for rendering a message with reply. This renders the message that the current message is a response to. If the current user is the one who responded to a specific message, the header text will be: “you replied to username”. Otherwise, it will be: “username replied to username”:

    // src/components/ChatBubbleWithReply.js
    import React from 'react';
    import { View, Text, StyleSheet } from 'react-native';
    import { MessageText, MessageImage, Time } from 'react-native-gifted-chat';

    const ChatBubbleWithReply = (props) => {
      const { position, children, currentMessage } = props;
      // if the position is right it means that it's the current user who sent the message
      const reply_header = (position == 'right') ? `you replied to ${currentMessage.reply_to}` : `${currentMessage.user.name} replied to ${currentMessage.reply_to}`;
      const reply_to_color = (position == 'right') ? '#d4d4d4' : '#a0a0a0';
      const reply_to_msg_color = (position == 'right') ? '#eee' : '#616161';

      return (
        <View style={styles[`${position}_container`]}>
          <View style={styles[`${position}_wrapper`]}>
            <View style={styles.reply_to_container}>
              <Text style={[styles.reply_to, { color: reply_to_color }]}>{reply_header}:</Text>
              <View style={styles.reply_to_msg_container}>
                <Text style={[styles.reply_to_msg, { color: reply_to_msg_color }]}>"{currentMessage.reply_to_msg}"</Text>
              </View>
            </View>
            <MessageText {...props} />
            {
              currentMessage.image &&
              <MessageImage {...props} />
            }
            {children}
            <Time {...props} />
          </View>
        </View>
      );
    }

Lastly, add the styles:

    const styles = StyleSheet.create({
      left_container: {
        flex: 1,
        alignItems: 'flex-start',
      },
      left_wrapper: {
        borderRadius: 15,
        backgroundColor: '#f0f0f0',
        marginRight: 60,
        minHeight: 20,
        justifyContent: 'flex-end',
      },
      right_container: {
        flex: 1,
        alignItems: 'flex-end',
      },
      right_wrapper: {
        borderRadius: 15,
        backgroundColor: '#0084ff',
        marginLeft: 60,
        minHeight: 20,
        justifyContent: 'flex-end',
      },
      reply_to_container: {
        marginRight: 5,
        marginLeft: 5,
        marginTop: 5
      },
      reply_to: {
        fontSize: 11
      },
      reply_to_msg_container: {
        marginRight: 10,
        marginLeft: 10,
        marginTop: 3
      },
      reply_to_msg: {
        fontStyle: 'italic',
        fontSize: 14
      }
    });

The ChatBubbleWithReply is based off the Bubble component that Gifted Chat provides.

Running the app

At this point, we’re now ready to run the app. Start by running the server:

    node server/index.js
    ~/ngrok http 5000

Update src/screens/Rooms.js file with the ngrok HTTPS URL:

    const CHAT_SERVER = "YOUR NGROK HTTPS URL";

Finally, run the app:

    react-native run-android
    react-native run-ios

To test the app, you need at least one room with two members. You can create them via the Chatkit app console. Once created, login with the user ID you used for each member, enter the room, and send a couple of messages using the first member. Then login with the second member and long press one of the messages to respond to it. The message you’re trying to respond to will be previewed in the footer area. From there, you can just compose your message like usual. Once it’s sent, it will now show a slightly different chat bubble UI.

Conclusion

That’s it! In this tutorial, you learned how to implement the “reply to message” feature that are commonly found in chat apps today. It’s a very simple feature but it’s a very useful tool to have for users.

As you’ve seen, Chatkit doesn’t actually support this feature natively. So we used multipart messages to add the information on which message the user is responding to. Then we used Gifted Chat to render that message using a custom chat bubble.

You can find the app’s source code on this GitHub repo.

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