Tutorial: How to Build a Slack Clone with React Native – Part 1

React Native has a significant footprint in the mobile development world. And with every new release, it gets better and better in terms of development speed and performance. Building a chat application used to be a massive chunk of work, but with the power of react-native and Stream Chat, it's possible to create a messaging app within minutes.

In this tutorial, we will build a clone of Slack, a messaging platform for workplaces. The Slack application comes with plenty of features. In this part of our tutorial, we will cover Slack’s following UI/UX features:

  • Channel list navigation
  • Input box
  • Message row
  • Reaction list
  • Giphy cards
  • Enriched URL previews

The result will look like the following:

Note: The objective of this tutorial is not intended to help you build a production-ready clone of the Slack application (because it already exists). Instead, this tutorial will serve as a go-to guide to help you understand how you can build real-life chat using UI components provided by Stream's Chat and Messaging API and SDKs.

If you feel lost during the tutorial, the following resources will be helpful:

Note If you want to send a message to the app, to check if the real-time feature is working or not, use this CodePen. Which has the same app that we have used in the current example with a different user.

Quick Test

If you would like to see the final state of the app in action quickly, please clone the following expo example of the slack clone and run it on the emulator or a phone:

Step 1: Setup

Dev Environment Setup

Before getting started, please make sure you have a development environment setup for react-native. Please read the Installing Dependencies section of the official react-native docs.

Project Setup

Once you have a dev environment setup, create a new react-native application:

Slack uses a Lato font, which is freely available on https://fonts.google.com/. For visual parity, we need to import the font into our app. To do so, create a file named react-native.config.js in the project directory and paste the following contents:

You can download Lato font files from the slack-clone project repository and icons from here.

Alternatively, you can download the fonts from google fonts website - https://fonts.google.com/specimen/Lato. You will see a button titled Download family at the top.

Next, prepare the following directory structure in the root directory of the project:

Please run the following command at this step:

$ npx react-native link

With these steps in place, this completes the setup required for your slack-clone app. You should now be able to run the app with the following command to launch the app on an emulator. Once started, you will see a welcome screen to React Native.

$ react-native run-ios

Step 2: Components

Basic Navigation Drawer

Lets first create a basic drawer navigation in our app. Replace the content of App.js with the following code:

After you’ve completed this, if you check your emulator, you should see the essential Slack-like drawer navigation.

Channel List Navigation

Now let's create a channel list navigation and add it to the drawer that we just created. For the Slack navigation drawer, some essential UI elements that we will focus on are the following:

  • Channels are grouped by
    • Unread channels
    • Channels (read channels)
    • Direct messages - this is perfect use case of SectionList in react-native
  • Unread channel labels are bold
  • Direct message users have a presence indicator next to their name - green if they are online, otherwise hollow circles.

Let's create a file named src/components/ChannelList.js. You can copy the contents of the following code snippet into your newly created file:

Additionally, replace ChannelListDrawer component in App.js with the following:

If you are familiar with react-native, this piece of code should be pretty straightforward. We have added a SectionList component with three sections: unread, channels, direct messages. You should see following in your app so far:

Now let's populate the SectionList with some channels. As I mentioned earlier in the tutorial, we are going to use Stream’s chat infrastructure.

So let's start by creating a Stream Chat client in App.js and pass it as a prop to the ChannelList component.

We have also added a prop function named changeChannel, which takes care of opening the channel screen and passing the provided channel ID to it. We will use this function as an onPress handler for the ChannelListItem.

Now let’s create a hook in ChannelList.js file, which takes care of querying channels. Later we will make them update in real-time when new messages arrive or we move messages between groups.

If you are not familiar with React hooks, here are some great resources to get started:

This hook queries the channels using the Stream client. It sorts them into three categories, which are returned as state variables: unreadChannels, readChannels, oneOnOneConversations

The renderChannelListItem function currently returns {channel.id}, which displays the ID of the channel. Let's create a proper UI for this item that resembles Slack.

Create a new component in a separate file named src/components/ChannelListItem.js.

This component will ensure different styles based on whether it's a group channel or one-on-one conversation, or if it's an unread channel. It will also check whether or not it contains user mentions.

Now let's use our ChannelListItem component in the ChannelList component’s SectionList.

As you can note here, I have supplied isUnread: true to unread section data. This way, I can tell the renderChannelRow function if the current channel to render is unread or not.

It's not necessary since you can quickly get an unread count of the channel in renderChannelRow using channel.unreadCount() to decide if it's read or unread. But it's just a way to avoid extra calls to channel.countUnread(), which essentially loops through messages.

If you reload your app, you should see a few channels populated in the channels list, as shown in the screenshot below:

So far, ChannelList works fine, but you will notice that it's not real-time. If a message is sent on some channel by another user, it won’t reflect on your ChannelList. We need to implement event handlers in our useWatchedChannels hook for this purpose.

You can find detailed docs about Stream events here.

We are going to handle two events for tutorial purpose, but you can experiment with as many events as you want:

  1. message.new - this event tells us that there is a new message on some channel (channel data is included in event object). In this case, we want to move the channel from either readChannels or oneOnOneConversations to unreadChannels.
  2. message.read - this event tells us that some channel (data available in event object) was marked as read. In this case, we want to move the channel from unreadChannels to either readChannels or oneOnOneConversations.

Replace the useWatchedChannels hook code with the following updated code:

We have added another useEffect hook here, which adds an event listener to our stream client, and also takes care of removing the listener when the component unmounts. The handleEvent is an event handler that will take some action based on which event was received.

As an exercise, you can try adding handlers for other events such as user.presence.changed, channel.updated or channel.deleted

Now try sending a message to some channel from this CodePen, (which uses the user Tommaso) and you should see the channel with a new message moving to the unread section.

Now the last thing we need to take care of is the onclick handler for ChannelListItem. When an item is selected, we need to update the channel in the ChannelScreen.

This concludes our ChannelList component. If you send a message to a channel in this list, you will see the event handler doing its job of updating the list UI accordingly.

Channel Screen

Let’s start by building the following channel header shown below:

Create a new file for header - src/components/ChannelHeader.js:

With this, we have added a hamburger icon on the left side of the screen, which, when clicked, will open the navigation drawer.

We are still yet to put this ChannelHeader in our ChannelScreen component.

Update the ChannelScreen component in App.js with the following:

If you reload your app, you should see empty channel screen with the header on top:

Now let's move onto adding MessageList and MessageInput components to our ChannelScreen.

These two components are provided by Stream as part of the react-native-sdk.

Please update the ChannelScreen component with the following:

After this change, you will see messages and an input box at the bottom of our Channel Screen.

But it doesn’t quite look like Slack messages. So now we need to make changes to make it look like Slack. Here is the list of things in Slack UI that separates it from our present UI in the app.

  1. The user name is shown at the top of the message
  2. Avatars (circular user profile pics next to message) should be squared
  3. Reactions should be at the bottom of the message
  4. Reaction counts should be shown next to each reaction
  5. URL preview should have thick left grey border and its content alignment offset
  6. All the messages should be displayed on the left side of the screen
  7. Giphys are shown differently in slack channels
  8. Date separator between messages should be shown above a grey line
  9. Send and attach buttons should be below the input box.

We will tackle these things one by one. Stream’s react-native SDK uses MessageSimple as the default message component. But you can also use a custom UI component as a message – reference here.

First, let’s add some basic custom theme styles. Let's create a custom message component (named MessageSlack) which internally uses MessageSimple with modifications. The MessageSimple component offers plenty of customizations. We are going to create our custom components for the following props, which are supported by the MessageSimple component.

Note: Please check the cookbook for more examples)

  • MessageAvatar
  • MessageFooter (which contains reactions)
  • MessageHeader (which contains the sender's username)
  • MessageText
  • UrlPreview (used to display enriched URL preview)
  • Giphy (used to show giphy cards)

Let's create each of these components:

src/components/MessageSlack.js

src/components/MessageFooter.js

src/components/MessageHeader.js

src/components/MessageText.js

src/components/MessageAvatar.js

src/components/UrlPreview.js

src/components/Giphy.js

We also need a custom DateSeparator component. The default that is used by Stream shows the date in the middle of a spacer/line. However, in the Slack UI, it is shown on top in a grey spacer/line.

src/components/DateSeparator.js

Now, after this, all you need to do is pass MessageSlack and DateSeparator to the MessageList component in App.js.

If you refresh the app, you will see the UI now has much better parity with the slack UI.

We still need to add some final finishing touches, such as the square avatar. The avatar should be aligned with the top of the message, and messages should not have borders, so we'll need to make some small alignment tweaks as well.

We will take care of them by theming the chat component. Please read the Custom Styles section of Stream’s react-native chat tutorial.

Create a file named src/stream-chat-theme.js:

Now pass this theme to the Chat component in the ChannelScreen within App.js, as shown below:

And that's it! You should see beautiful Slack-like messages on the screen. 😺

Input box

Now let's move on to the input box at the bottom. The MessageInput component (from Stream) accepts Input as a custom UI component prop to be shown for the input box. Let's create this custom component in src/components/InputBox.js.

The following components that we have used in the InputBox are provided by Stream’s react-native SDK, which takes care of plenty of things for us:

  • AutoCompleteInput - takes care of all features of the input box such as mentions, sending messages, maintaining enabled/disabled state, etc.
  • SendButton
  • AttachButton

All we have done is shuffle around the internal components of the MessageInput.

It is important to note here that you must pass the entire prop object to AutoCompleteInput, SendButton, and AttachButton. Hence, all the handlers present in MessageInput are accessible to these components.

Now pass this InputBox component to MessageInput in the ChannelScreen component of App.js.

The final version of the ChannelScreen component is as follows:

Note: the extra prop to MessageInput component - additionalTextInputProps. That is to modify the placeholder of the input box.

Conclusion

This concludes part one of our tutorial on how to build a Slack clone using Stream’s React Native Chat Components. I hope you found this tutorial useful, and I look forward to hearing your feedback.

In the next part of the tutorial – which will be published later – we will cover additional UI components and their functionality, such as:

  • Threads
  • Channel search
  • Action sheets
  • Unread message notifications
  • And more!

If you run into any issues implementing portions of the Slack clone that we’ve built in this tutorial, please leave a comment below. As the author, I’ll do my very best to respond to everyone. Your feedback is much appreciated!

Here are a few links to help you if you get stuck along the way:

Happy coding!

TutorialsChat