Basic knowledge of React, React Native, and Node.js is required to follow this tutorial. It assumes that you already have the React Native development environment installed on your machine.
You also need to have your own Google account. We will be using it to setup Firebase. Lastly, you need to have your own CometChat Pro and ngrok account. Don’t worry, it’s free to sign up for both services.
Setting up a CometChat app
On your CometChat Pro account dashboard, click on Add New App. On the modal window that pops out, fill in the necessary details then click Add App:
Once the app is created, you’ll be provided with the app credentials. We’ll specifically be using the app ID, region, and auth key to connect to CometChat’s API.
Creating a new React Native project
Create a new React Native project by executing the following command:
Installing the dependencies
Next, we need to install the dependencies:
Here’s a break down of the packages we just installed:
@cometchat-pro/react-native-chat - this is the React Native SDK for CometChat. It exposes APIs for implementing chat functionality.
@cometchat-pro/react-native-calls - the WebRTC library for CometChat. We’re not really going to implement video calls in this app, but this is just a required install when using the React Native SDK.
@react-native-firebase/app - the core library for providing Firebase functionality for React Native apps.
@react-nati ve-firebase/auth - the library for providing Firebase auth functionality.
@react-native-firebase/firestore - the library for providing Firebase Firestore functionality.
react-native-paper - for providing a nice looking design to the app.
@react-navigation/native - for providing navigation functionality to the app.
@react-navigation/stack - the stack navigator for React Navigation.
@react-navigation/bottom-tabs - the bottom tabs navigator for React Navigation.
@react-native-async-storage/async-storage - for storing data locally.
axios - mainly used for making requests to the Node.js server.
expo-status-bar - for styling the status bar.
react-native-config - allows us to use .env files for storing environment variables for the app.
react-native-vector-icons - for using icon fonts.
The other libraries not mentioned here but are in the package.json file are peer dependencies for the main packages that we’re using. More specifically, they’re the dependencies of Comet Chat and React Navigation.
Once all the dependencies are installed, execute npx pod-install. This will install all the native iOS dependencies required by the packages we just installed. For packages that require manual linking, you need to execute npx react-native link packagename.
In order to setup CometChat, please check the React Native setup guide. After following the guide, you can also check the sample app on this GitHub repo. Check to see if you have the same configuration, especially for android/build.gradle and android/app/build.gradle.
Setting up Firebase
We’ll need Firebase to handle our authentication and database needs. We’ve already installed Firebase above, but we still need to configure it for it to work. Follow the instructions on the rnfirebase (Firebase package for React Native) documentation to set it up:
This section is optional. Go through it only if you’re having issues with running your project.
Next, you’ll probably get an error with Firebase Auth:
To solve it, you have to supply the --repo-update option when executing pod install:
Vector Icons Issue
The next issue you’ll probably get is the Unrecognized font family error returned by React Native Vector Icons. It happens when you don’t have the assets linked on the iOS project.
Once you’ve done that, you’ll most likely get another issue that looks like the following:
That has to do with the auto-linking feature introduced in React Native 0.60. React Native Vector Icons, at the time of this writing, hasn’t been updated yet to utilize the auto-linking feature. That’s why we still had to use npx react-native link to link it. This copies the font files into the bundle resources. We already executed npx pod-install earlier which copies the font files into the pod resources. This causes duplicate resources. Thus the solution is to remove the font files added by the npx react-native link. You can do that by opening the project on Xcode (the .xcworkspace file on the ios folder). Go to the Build Phases tab and under Copy Bundle Resources delete all the font files you see by clicking on the minus button. You can select multiple files before doing that:
React Native Config Issue
On Android, React Native Config has an additional step to get it working on Android. Add this on the second line of your android/app/build.gradle file:
Building the app
We’re ready to start building the app. But first, create a .env file and put in your CometChat app credentials:
Next, create a src/config.js file. We’ll supply the value for the BASE_URL later:
Once that’s done, we’re now ready to start coding. First, open the index.js file and update its contents with the following. This is the entry-point of the app, it’s also where we initialize the connection to CometChat:
Next, replace the contents of the App.js file with the following. Here, we’re simply rendering the status bar and the Root component:
Create a src/Root.js file. This is where we setup the theme for React Native Paper to use. We’re also wrapping the Home component in the UserContextProvider. This allows us to have global state that can be accessed from anywhere in the app:
Here’s the code for the user context. It’s where we store user details such as their unique ID, name, and gender. This is also where we store their matches and reset all of that data once they sign out:
Next, create the src/Home.js file. Here, we wrap everything in a Stack navigator. The screens being rendered depends on whether a user is currently logged in or not. We only render the MainTabScreen (contains the Match, ChatList, and Chat screens) if a user is currently logged in. Otherwise, we render the Login and Register screen.
We’re using Firebase Auth to listen for when the auth state changes. The onAuthStateChanged() function is executed every time a user is logged in or logged out. If a user logs in, we use Firebase Firestore to fetch the user data via the unique ID assigned by Firebase Auth. We then use it to set the user’s id, name and gender to the global state by using the setUser() function. We will use these data later on once we get to the Match screen:
Here’s the code for the SplashScreen. We show this screen when the app is still initializing. This simply renders a loading indicator:
The register screen is where users can sign up for an account. We use Firebase Auth and Firestore to store user data, then we create a corresponding user on CometChat. This way, all users have the ability to chat from the time they sign up.
Start by importing the libraries and modules we need. The usersCollection is the Firestore collection we use for storing users data. Note that it doesn’t already need to exist when you refer to it. It will automatically be created once you add your first document:
Next, create the component. Here we need four local states for storing the name, email, password, and gender of the user. These corresponds to each of the fields that the user has to fill up. When the user clicks on the Register button, we do four things:
Create a user on Firebase using the auth().createUserWithEmailAndPassword() method. This accepts the user’s email and password as arguments. A successful response will yield a user.uid which is the unique ID assigned by Firebase to the created user. This is the ID that we will be using for all of the transactions in this app.
Add a new document to the users collection in Firestore. This is where we add all of the user’s data since we can’t really do that with Firebase Auth.
Make a request to the server (we will create this one later in the Adding the Server Code section) to create the corresponding CometChat user.
Call setIsLoggedIn() method from the context to update the UI that the user is logged in. Note that the request to create the CometChat user on the server has to have await because calling setIsLoggedIn() will trigger CometChat.login() so if the corresponding CometChat user doesn’t exist yet at that point then you’ll get an error.
Here’s the UI for the register screen. Here, we have the user fields, a button to register, and another button to navigate to the login screen:
Lastly, add the styles:
Here’s the code for the login screen. We log the user in using Firebase’s auth().signInWithEmailAndPassword() method. Then we use the setIsLoggedIn() method from the global state to toggle the user’s login state. That way, the isLoggedIn condition on the Home.js file will be updated to true. This will then render the main tab screen instead of the login screen or register screen. And because the match screen is rendered by main tab screen by default, we don’t really need to navigate to the match screen manually:
The main tab screen serves as a wrapper for the main screens of the app: the match screen and chat screen. It makes use of a bottom tab navigator provided by React Navigation. This is also where we login the CometChat user. That way, the connection is already initialized for their account before they get to the chat screen. Note that the userId in the context has both uppercase and lowercase letters on it. CometChat only stores user IDs in lowercase, thus we need to use the toLowerCase() method when logging the user in:
Match Stack Screen
The Match Stack screen serves as the container for the Match screen. It’s main purpose is to provide a header for the Match screen. If we just added it directly as a standalone screen in MainTab screen then it won’t render a header because we’ve set headerShown: false in Home.js. Here, we’ve also included a LogoutButton which we’ll create shortly:
Logout Button Component
The LogoutButton component allows the user to logout the corresponding Firebase and CometChat user out of the app. As you have seen in the MatchStack screen earlier, we will include this as a headerRight on all screens shown to authenticated users. That way, they can easily log out anytime:
Now we proceed with the main part of this tutorial. First, let’s implement the match screen where users find their potential date.
Here’s how it works:
Fetch the users that were already seen by the current user.
Filter out those users from the users collection. That way, the current user won’t see them again. Aside from that, we also filter out those of the same gender (Note: to keep things simple, we’re only catering for the basic sexual orientations in this app).
Update the state with the users fetched from Firestore.
When the user likes or dislikes someone, their preference is saved in the matches collection.
If the user likes someone, Firestore is queried if the user is already liked by the person they’ve just liked.
If there’s a match then it means the like is mutual. At this point, two new documents are created on the chats collection. Each one pertains to the users who matched. This way, the chat list screen can just query for those to get the users whom they can chat with.
You now know how it works so let’s go ahead and implement it.
First, import the modules we need and initialize the Firestore collections we’re going to use:
Next, create the component. The matchedUsers is an array from the UserContext. These are the users that have already been seen by the current user. addMatchUser() is a method for adding a user to that array. The userId, userName, and gender is the data of the currently logged in user.
The potentialMatches are stored in the local state. These are the users that are yet to be seen by the logged in user:
Next, we implement steps 1 to 3 of the summary earlier. We use the not-in selector to exclude the users that have already been seen by the logged in user. And the == selector to get only those of the opposite gender. If you’re wondering why we didn’t use != instead, that’s a limitation by Firestore. You can’t use not-in with !=, thus the need for the opposite_gender variable. The not-in selector also doesn’t accept an empty array that’s why only the gender is used as a filter when the previousMatches is empty. Once users are fetched, we get their auth_uid, name, and gender. These are used as the data for each potential match:
Next, add the decide() function. This implements steps 4 to 6 from the summary earlier. It’s executed when the user likes or dislikes someone. The user data (person) and their decision (like or dislike) are passed as arguments. addMatchedUser() adds the person’s ID to the matchedUsers array so it gets excluded from the potential matches. The state is volatile though, so we also have to add a new document to the matches collection containing the logged in users ID, the person they liked or disliked, and their decision. That way, even if the user closes the app, their matches are still intact.
If the logged in user decided to “like” the person, we query Firestore to see if the person also liked the logged in user. If there is a result, then the like is mutual. We alert the logged in user of this and save two new documents to the chats collection. Each pertaining to the users who just matched. We’re saving both the user ID and the name so we can easily use it later to render the names the logged in user has matched with:
Next, render the UI. We’re making use of the PersonCard component to render the current person being shown to the logged in user:
Lastly, export the component and add the styles:
Person Card Component
Here’s the code for the Person Card component. This makes use of React Native Paper’s Card component. It shows the person’s name, avatar, and two buttons for the decision:
Chat Stack Screen
Now that we’re done with the match screen, it’s time to implement the chat feature. These are two separate screens so we need to use a stack navigator to navigate between the two screens: chat list and messages. Note that we’re setting headerShown to false for the the Messages screen. This is because we won’t be needing it as we will be using the header component from the UI kit later on:
Chat List Screen
The chat list screen is responsible for listing all the people who have matched with the logged in user. Once the component is mounted, we query the chats collection in Firestore to fetch all the users who have matched with the logged in user. We then update the state with those users:
Here’s the code for rendering the list:
Here’s the navigateToChat() function. Here, we pass in the matchId which is the ID of the user whom the logged in user has matched with:
We now proceed to the messages screen. This is where we use the CometChat React Native UI kit to easily implement chat:
Let’s break the above code down. First, we import the main CometChat library for React Native. You’ve already seen it in action earlier on the MainTabScreen. We used it to login the CometChat user:
Next, we import the CometChat React Native UI Kit. This enables us to easily build the chat UI. Documentation for it is available here: React Native UI Kit. Basically, all you need to do to use it is download the zip archive from the GitHub repo, extract it, move the resulting folder to your project’s src directory, then rename the folder to cometchat-pro-react-native-ui-kit.
Next, you need to install the peer dependencies on its package.json file to the main project. They’re all indicated in the docs. If that doesn’t work then check the peerDependencies on the package.json file to see if there are any dependencies that the docs have missed. Once all the dependencies are installed and setup properly, the UI kit should work flawlessly.
Going back to the code, when the component is first mounted, we get the currently logged CometChat user and set it in the local state. We do the same for the user they’re chatting with:
Once that’s done, it’s only a matter of using the UI components available to the UI kit. In this case, we only want to use the CometChatMessages component. This will display a header containing the details of the user they’re chatting with. Then below it is the usual chat UI that you see on messaging apps. This component requires the item, loggedInUser, and actionGenerated prop to be passed in. It’s basically a callback function that gets called when the user performs specific actions such as when viewing an image or when the menu header is clicked. In this case, we’re simply setting it to an empty function since we don’t really want to perform any actions. Lastly, you need to pass in a navigation prop with the value being the navigation object from React Navigation. This will allow us to use the main navigation of the app instead of the UI kit’s navigation:
Modifying the React Native UI Kit
We’ll need to make a few modifications to CometChat’s UI kit so that it matches our requirements. The root directory we’ll be working on is src/cometchat-pro-react-native-ui-kit.
Remove the live reaction button
We only want this to be a simple chat app, so to keep things simple, we need to remove the live reaction button:
To remove it, open src/components/Messages/CometChatMessageComposer/index.js and remove the following code:
Remove composer actions button
Still on the src/components/Messages/CometChatMessageComposer/index.js file, remove the following code. This is the first child of style.mainContainer and it comes right before style.textInputContainer:
Remove message reactions
The last thing that we need to remove is the message reactions. This is the emoji button that you see right below the timestamp of each message bubble:
We need to update two files to remove it. That’s because the receiver and the sender has separate code for each. First, let’s update the receiver version at src/components/Messages/CometChatReceiverTextMessageBubble/index.js. Remove the following code. This is located near the bottom, right before all the View components are closed:
Next, update the sender version at src/components/Messages/CometChatSenderTextMessageBubble/index.js. Remove the following code. This is located right before the final closing View tag:
Adding the server code
The main purpose of the server is to create a corresponding CometChat user for the Firebase user. To make things simple, we’ll use Node.js and Fastify to create the server.
On your working directory, execute the following command:
Just press enter for every question to use the defaults.
Next, install the dependencies with the following command:
Next, create a .env file and add your CometChat credentials:
Note that the COMETCHAT_API_KEY should be the keys you can find on the API & Auth Keys section under the Rest API Keys tab:
Create an index.js file and add the following:
Add a route you can use for testing if the server is running:
Next, add the route for creating users. As you’ve seen in the app code earlier, a POST request is made to this route when a user signs up. This accepts the Firebase user auth ID (uid) as well as the Firebase document ID. The name, email, and gender is also sent in the request. We use robohash for the user avatars. Once we have all the user data ready, we send the request to the CometChat REST API endpoint. Be careful with this because you might be led to an earlier documentation (and thus an incorrect API endpoint). Always make sure you’re selecting the stable version of the documentation (currently it’s at version 2.1). The important thing to remember when sending the request is that it should be converted to a JSON string using JSON.stringify. The metadata should also be converted separately:
Run the server on port 3000:
Running the app
Now we’re ready to run the app. Start by running the server:
Then use ngrok to expose the server to the internet:
Update the src/config.js file of the app to use the ngrok HTTPS URL:
Finally, run the app:
Register a few accounts you can use for testing then try it. Remember that you can only chat with another user if you both liked each other.
That’s it! In this tutorial, you’ve learned how to create a dating app with React Native. Specifically, you’ve learned how to use Firebase to implement authentication within a React Native app. We’ve also use it as a data storage for users data. Then we used CometChat to easily implement the chat functionality.
You can view the source code of the app in this GitHub repo.
About the author
Wern is a web and mobile app developer from the Philippines. He loves building apps and sharing the things he has learned by writing in his blog. When he's not coding or learning something new, he enjoys working out and gardening.