🎉 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

Adding gifs and emojis to a React Native chat app

  • Wern Ancheta

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

One of the most common features that chat apps usually have is the ability to search for gifs and send them as part of the message. Gifs are a fun way to represent what the sender is currently feeling. When combined with emojis, they can really add life to a chat room.

In this tutorial, we’re going to take a look at how you can add gifs and emojis to a React Native chat app.

Prerequisites

Basic knowledge of React Native is required to follow this tutorial.

Chatkit is used for providing the chat functionality. This tutorial assumes that you already have an existing Chatkit app instance. If not, then you can sign up here and create one.

The following versions are used for building the app. Be sure to switch to those versions if you encounter any issues in 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’re going to update an existing chat app created with Chatkit and Gifted Chat. We’ll add the gif search functionality using Giphy API, and the emojis using the React Native Emoji Selector package.

Here’s what the app will look like:

You can find the full source code of the app on this GitHub repo.

Bootstrapping the app

As mentioned earlier, we’re going to update an existing app so all you have to do is clone the repo and set up the starter code:

    git clone https://github.com/anchetaWern/RNChatGifAndEmojis.git
    cd RNChatGifAndEmojis
    git checkout starter
    yarn
    react-native eject
    react-native link react-native-config

The above commands will install all the dependencies, re-create the android and ios folders, and link the native modules. In this case, we only have one which is React Native Config. We use it for reading configuration from a .env file.

React Native Config has an extra step for Android. Add the following as the second line in your android/app/build.gradle file:

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

Aside from that, we’re also relying on the following modules:

Setting up a Giphy app

In order for us to have access to the Giphy API, we first have to create a Giphy app instance. If you don’t already have a Giphy account, you can log in with your Facebook account or join here.

Once you’re logged in to your Giphy account, go to the developer dashboard and click on the Create an App button. That will open a modal which looks like the following. Enter the details of the app and click Create New App to create it:

Once created, you should see the app listed on your dashboard. Take note of the API key as that’s the one we’re going to use to make requests later on:

Building the app

Now we’re ready to start building the app. We’ll first start by building the main App component before we proceed to the component for searching gifs.

Main component

On your main app component, start by importing the additional React Native modules that we’re going to need:

    // App.js
    import { 
      StatusBar, 
      SafeAreaView, 
      View, // add this
      Text, // add this
      TouchableOpacity, // add this
      StyleSheet 
    } from 'react-native';

Next, import the modules we installed earlier as well as the custom component and helper that we’ll create later:

    import Modal from 'react-native-modal';
    import EmojiSelector from 'react-native-emoji-selector';

    import GiphySearch from './src/components/GiphySearch'; // component for searching for gifs
    import searchGifs from './src/helpers/searchGifs'; // for getting gif data via the Giphy API

For the initial state, we also need to add a few:

    class App extends Component {

      state = {
        messages: [],

        // add these:
        text: '', // for storing the text to be sent by the user
        is_gif_modal_visible: false, 
        is_emoji_modal_visible: false,
        query: '', // the query text for searching gifs
        gif_url: '', // the URL of the gif selected by the user
        has_emoji: false // if the user has picked an emoji or not
      };

      //
    }

In the render() function, add the modals that contains the components for searching gifs and emojis. Each modal has a close button for setting the visibility of the corresponding modal to false. Aside from the modals, we also add the renderActions prop to Gifted Chat. This is used for rendering extra action buttons to the left of the text field for entering messages. These action buttons are used for opening the modals. The text and onInputTextChanged props are also added to Gifted Chat because we now need to manage the state for storing the text. This has to do with the addition of emojis, as you’ll see later:

    render() {
      const { 
        messages,

        // add these:  
        text,  
        is_gif_modal_visible, 
        is_emoji_modal_visible, 
        query, 
        search_results 
      } = this.state;

      return (
        <Fragment>
          <StatusBar barStyle="light-content" />
          <SafeAreaView style={styles.container}>
            <GiftedChat
              text={text}
              onInputTextChanged={text => this.setState({ text })}
              messages={messages}
              onSend={messages => this.onSend(messages)}
              user={{
                _id: this.user_id
              }}
              renderActions={this.renderCustomActions}
            />
          </SafeAreaView>

          <Modal isVisible={is_gif_modal_visible}>
            <View style={styles.modal_body}>
              <TouchableOpacity onPress={() => this.closeGifModal('')}>
                <View style={styles.modal_close_container}>
                  <Text style={styles.modal_close_text}>Close</Text>
                </View>
              </TouchableOpacity>

              <GiphySearch
                query={query}
                onSearch={(query) => this.setState({ query })}
                search={this.searchGifs}
                search_results={search_results}
                onPick={(gif_url) => this.closeGifModal(gif_url)} />
            </View>
          </Modal>

          <Modal isVisible={is_emoji_modal_visible}>
            <View style={styles.modal_body}>
              <TouchableOpacity onPress={() => {
                this.setState({ is_emoji_modal_visible: false });
              }}>
                <View style={styles.modal_close_container}>
                  <Text style={styles.modal_close_text}>Close</Text>
                </View>
              </TouchableOpacity>

              <EmojiSelector
                columns={12}
                showHistory={true}
                onEmojiSelected={this.selectEmoji}
              />
            </View>
          </Modal>
        </Fragment>
      );
    }

Before we proceed with the rest of the code, let’s first take a look at the GiphySearch component. This is a custom component which we’ll create later. It accepts the following required props:

  • query - the user’s query text for searching gifs.
  • onSearch - the function for updating the user’s query text.
  • search - the function for searching gifs.
  • search_results - the results to render. This is an array of objects containing all the relevant data that allows us to render each gif.
  • onPick - the function for setting the gif selected by the user.
    <GiphySearch
      query={query}
      onSearch={(query) => this.setState({ query })}
      search={this.searchGifs}
      search_results={search_results}
      onPick={(gif_url) => this.closeGifModal(gif_url)} />

Next is the EmojiSelector component.

    <EmojiSelector
      columns={12}
      showHistory={true}
      onEmojiSelected={this.selectEmoji}
    />

Note: You may get a warning regarding the use of the AsyncStorage in React Native itself. This is because we’ve set showHistory to true. That specific feature uses AsyncStorage, but the module is still using the one that comes with React Native. AsyncStorage is now part of React Native Community because of the Lean Core effort. If you’re using this component on production, it’s better if you just clone a copy and make the necessary adjustments.

Now let’s go back to adding the rest of the code. Here’s the code for rendering the custom action buttons. The text color will change depending on whether the user has selected an input for that particular modal or not. This informs the user that they have selected something and it will be part of the message that will be sent:

    renderCustomActions = () => {
      const { gif_url, has_emoji } = this.state;
      const gif_text_color = (gif_url) ? "#0064e1" : "#808080";
      const emoji_text_color = (has_emoji) ? "#0064e1" : "#808080";

      return (
        <View style={styles.custom_actions_container}>
          <TouchableOpacity onPress={() => this.setState({ is_gif_modal_visible: true })}>
            <View style={styles.buttonContainer}>
              <Text style={{ color: gif_text_color }}>GIF</Text>
            </View>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => this.setState({ is_emoji_modal_visible: true })}>
            <View style={styles.buttonContainer}>
              <Text style={{ color: emoji_text_color }}>ICO</Text>
            </View>
          </TouchableOpacity>
        </View>
      );
    }

Next, add the searchGifs() function. This uses the helper function of the same name to get the search results:

    searchGifs = async () => {
      const { query } = this.state;
      const search_results = await searchGifs(query);
      this.setState({
        search_results
      });
    }

The closeGifModal() function is executed when the user picks a gif from the results or closes the modal for searching gifs:

    closeGifModal = (gif_url) => {
      this.setState({
        is_gif_modal_visible: false,
        gif_url
      });
    }

The selectEmoji() function is executed when the user selects an emoji from the EmojiSelector component. When they do, we append that emoji to the current text being composed by the user. We’re also setting has_emoji to true so that the text color of the custom action button changes to indicate that the user has selected an emoji:

    selectEmoji = (emoji) => {
      this.setState(state => {
        return {
          text: `${state.text} ${emoji}`,
          has_emoji: true
        }
      });
    }

Next, update the onSend() function so that the gif picked by the user is included in the message. Since we’re already using Chatkit’s sendMultipartMessage() function, all we have to do is push an additional object to the message_parts array which contains the gif image. To include a URL, we need to specify the type and url. The type is the MIME type of the resource which the URL points to. Once the message is sent, don’t forget to reset the state so the same gif isn’t sent again on the next message:

    onSend = async ([message]) => {
      let text_content = message.text;
      // add these:
      const { gif_url } = this.state;

      let message_parts = [
        { type: "text/plain", content: text_content }
      ];

      if (gif_url) {
        message_parts.push({ type: "image/gif", url: gif_url });
      }

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

        // add these:
        this.setState({
          gif_url: '',
          has_emoji: false
        });
      } catch (send_msg_err) {
        console.log("error sending message: ", send_msg_err);
      }
    }

We also need to update the getMessage() function. This is the function for extracting and formatting the message data required by Gifted Chat to render a message bubble. We need to update it so it extracts the gif URL if it’s available and add the image property to the message so Gifted Chat can render a preview:

    getMessage = ({ id, sender, parts, createdAt }) => {
      const text = parts.find(part => part.partType === 'inline').payload.content;
      const url_part = parts.find(part => part.partType === 'url') ? parts.find(part => part.partType === 'url').payload : null; // add this

      let msg_data = {
        _id: id,
        text: text,
        createdAt: new Date(createdAt),
        user: {
          _id: sender.id,
          name: sender.name,
          avatar: `https://ui-avatars.com/api/?name=${sender.name}&background=0D8ABC&color=fff`
        }
      }

      // add this:
      if (url_part) {
        msg_data.image = url_part.url;
      }

      return {
        message: msg_data
      };
    }

Lastly, add the styles:

    const styles = StyleSheet.create({
      container: {
        flex: 1
      },
      custom_actions_container: {
        flexDirection: "row",
        justifyContent: "space-between"
      },
      buttonContainer: {
        padding: 10
      },
      modal_body: {
        flex: 1,
        backgroundColor: '#FFF',
        padding: 10
      },
      modal_close_container: {
        alignSelf: 'flex-end',
        marginTop: 10,
        marginRight: 10
      },
      modal_close_text: {
        color: '#0366d6'
      }
    });

searchGifs helper

The searchGifs helper is what we used in the searchGifs() function on App.js earlier. This is responsible for making a request to the Giphy API based on the user’s query. Once the results are returned, it extracts the data that we need. In this case, we only need the id (to use as key for the FlatList) and url. We’re using the images.preview_gif.url since it’s the smallest gif available:

    // src/helpers/searchGifs.js
    import Config from 'react-native-config';
    const GIPHY_API_KEY = Config.GIPHY_API_KEY;
    const giphy = require('giphy-api')(GIPHY_API_KEY);

    const searchGifs = async (query) => {
      const res = await giphy.search(query);
      const gifs = res.data.map((item) => {
        return {
          id: item.id,
          url: item.images.preview_gif.url
        };
      });
      return gifs;
    }

    export default searchGifs;

GiphySearch component

The GiphySearch component is responsible for rendering the text input for entering the user’s query and the search results. It’s a functional component so all of its data is dependent on the props that are passed to it. The search is triggered when the Search button is clicked. Once the result becomes available, they are rendered in the FlatList:

    // src/components/GiphySearch.js
    import React, { Fragment } from 'react';
    import { View, FlatList, TextInput, Button, TouchableOpacity, Image, StyleSheet } from 'react-native';

    const GiphySearch = ({ query, onSearch, search, search_results, onPick }) => {

      return (
        <Fragment>
          <View style={styles.container}>
            <View style={styles.input_container}>
              <TextInput
                style={styles.text_input}
                onChangeText={onSearch}
                value={query}
                placeholder="Search for gifs"
              />
            </View>

            <View style={styles.button_container}>
              <Button title="Search" color="#0064e1" onPress={search} />
            </View>
          </View>
          {
            search_results &&
            <FlatList
              data={search_results}
              renderItem={({ item }) => {
                return (
                  <TouchableOpacity onPress={() => {
                    onPick(item.url);
                  }}>
                     <View>
                      <Image
                        resizeMode={"contain"}
                        style={styles.image}
                        source={{uri: item.url}}
                      />
                    </View>
                  </TouchableOpacity>
                );
              }}
              keyExtractor={(item, index) => item.id}
              numColumns={2}
              columnWrapperStyle={styles.list}
            />
          }
        </Fragment>
      );
    }

Lastly, add the styles:

    const styles = StyleSheet.create({
      container: {
        flexDirection: 'row',
        justifyContent: 'space-between',
        padding: 5
      },
      input_container: {
        flex: 2
      },
      text_input: {
        height: 35,
        marginTop: 5,
        marginBottom: 10,
        borderColor: "#ccc",
        borderWidth: 1,
        backgroundColor: "#eaeaea",
        padding: 5
      },
      button_container: {
        flex: 1,
        marginTop: 5
      },
      list: {
        justifyContent: 'space-around'
      },
      image: {
        width: 150,
        height: 150
      }
    });

    export default GiphySearch;

Note that on Android, gifs aren’t supported by default. You have to update your android/app/build.gradle file and add the module for rendering gifs:

    dependencies {
      compile 'com.facebook.fresco:animated-gif:1.10.0' // add this
    }

Running the app

At this point, you’re now ready to run the app. Go ahead and do the following on your Chatkit app instance:

  1. Enable test token provider.
  2. Create a user and a room that you can use for logging in.

Once you’re done, update the App.js and add a value to USER_ID and ROOM_ID:

    import searchGifs from './src/helpers/searchGifs';

    const USER_ID = 'YOUR CHATKIT USER ID';
    const ROOM_ID = 'YOUR CHATKIT PUBLIC ROOM ID';

If you haven’t done so already, add your Chatkit app credentials and Giphy API key to the .env file:

    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"
    GIPHY_API_KEY="YOUR GIPHY API KEY"

Finally, you can run the app:

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

Conclusion

In this tutorial, you learned how to add a gif search functionality to your Chatkit chat app built with React Native. Specifically, you learned how to use of the Giphy API to search for gifs.

You can find the code used in this tutorial on this GitHub repo.

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