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:

Stream Chat - Final Slack Clone - Preview

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:

https://gist.github.com/nparsons08/f4d2ed23e943467eddd30d7d43a63ab3

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:

https://gist.github.com/vishalnarkhede/0d5baabc8eb3241a2c2acdf0f08c921b

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:

https://gist.github.com/vishalnarkhede/fb09f427d47f94a06324f475b0b9e7d0

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:

Stream Chat Clone - Directory List

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

React Native - Welcome Screen

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:

https://gist.github.com/vishalnarkhede/0095ad6050a8fdefd47dfefeffe9a844

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

Stream Slack Clone - Getting Started

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:

https://gist.github.com/vishalnarkhede/46f946175ab6b06fec48200717411265

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

https://gist.github.com/vishalnarkhede/a8eb0830e291520ab8f47af243a102b0

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:

Stream Chat Clone - Base

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.

https://gist.github.com/vishalnarkhede/bac80bfd2c159bb62fe95e560f1d65ea

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:

https://gist.github.com/vishalnarkhede/7a18641dd5cc94e7e0c4083293d39b39

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 <Text>{channel.id}</Text>, 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.

https://gist.github.com/vishalnarkhede/43dfffc6e1d3001de472a7b05b230fc2

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.

https://gist.github.com/vishalnarkhede/6fd54659cc3bcffef68560caa8d69f16

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:

Stream Chat Slack Clone - Notification Indicator

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:

https://gist.github.com/vishalnarkhede/5111c087524dc4e457198ecbcd451e62

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:

Stream Chat Slack Clone - Channel Name

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

https://gist.github.com/vishalnarkhede/28425f843ff4f4a507ca0e089a2ddde4

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:

https://gist.github.com/vishalnarkhede/ba390d7bf01f40bcb53cb6fe85e04c2e

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

Stream Chat Clone - Empty Channel

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:

https://gist.github.com/vishalnarkhede/5208c392b246f10784cd660c4949b22a

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

Stream Chat Slack Clone - Reactions

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.

Stream Chat Slack Clone - Message

  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
https://gist.github.com/vishalnarkhede/f7b4bf02de3772c25631f3692c64d024

src/components/MessageFooter.js
https://gist.github.com/vishalnarkhede/e30097aeb24ef1a5d506053b6cda9417

src/components/MessageHeader.js
https://gist.github.com/vishalnarkhede/4d30732665c7fd85dc4e884d48247a5f

src/components/MessageText.js
https://gist.github.com/vishalnarkhede/66d0ca4f7bfc0292cc0a96af15fcbda3

src/components/MessageAvatar.js
https://gist.github.com/vishalnarkhede/301e101d51f2fb7e13086bab54e94da3

src/components/UrlPreview.js
https://gist.github.com/vishalnarkhede/a287efc6637a4a3166ebbac14f6d56ba

src/components/Giphy.js
https://gist.github.com/vishalnarkhede/49deb8edb23d2ac07bb075718bb125e0

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
https://gist.github.com/vishalnarkhede/bd682b394c7dc9a606484f97a548b896

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

https://gist.github.com/vishalnarkhede/20407f709445ccac9d5a487d0f84a917

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:

https://gist.github.com/vishalnarkhede/8bcbb6de14f271aa22dec3e48e17c810

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

https://gist.github.com/nparsons08/9af0bbfca4373e012da7eaf5220bdf55

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

Stream Chat Slack Clone

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.

https://gist.github.com/vishalnarkhede/8ce5dec78730ab283c74bfa4ce9cfee9

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.

https://gist.github.com/nparsons08/5f7e1bd9156e117eda5aa7f31a6fa78d

The final version of the ChannelScreen component is as follows:

https://gist.github.com/vishalnarkhede/ff57edf4c77b6cde117044a6ff97e42e

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

Stream Chat - Final Slack Clone

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