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

Vishal Narkhede
11 min readApr 21, 2020

--

🎉 Announcement 🎉: Please checkout v2 of slack clone tutorial, with new design and UX of slack here - https://dev.to/vishalnarkhede/how-to-build-slack-clone-with-react-native-part-2-g5

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, 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 — https://codepen.io/vishtree/pen/YzyyEBG?editors=0010. 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:

git clone git@github.com:GetStream/slack-clone-expo.gitcd slack-clone-expoyarn; yarn start

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 project directory and paste the following contents:

You can download Lato font files from the slack-clone project repository

Alternatively you can download the fonts from google fonts website — https://fonts.google.com/specimen/Lato. You will see a button named Download Family at 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 launched, you will see a welcome screen for React Native.

react-native run-ios
Welcome screen

Step 2: Components

- Basic Navigation Drawer

Let’s first create a basic drawer navigation in our app. Replace the content of App.js with 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:

  1. Channels are grouped by (perfect use case of SectionList in react-native)
  • Unread channels
  • Channels (read channels)
  • Direct messages

2. Unread channel labels are bold

3. 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 following code snippet into your newly created file:

Additionally, replace ChannelListDrawer component in App.js with 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 <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.

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 details docs about Stream events in documentation — https://getstream.io/chat/docs/event_listening/.

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 — https://codepen.io/vishtree/pen/YzyyEBG?editors=0010, (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 theonclick handler for ChannelListItem. When an item is selected, we need to update the channel in ChannelScreen.

And 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 thefollowing channel header as shown below:

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 component 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 header on top:

Now let’s move onto add 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 components 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.

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

We will tackle these things one by one. Stream’s react-native SDK uses MessageSimple as default message component. But you can also use a custom UI component as a message (reference here - https://github.com/GetStream/stream-chat-react-native/tree/v0.14.0/docs/cookbook.md#how-to-customize-message-component ).

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

  • MessageAvatar
  • MessageFooter (which container reactions)
  • MessageHeader (which contains message user’s name)
  • MessageText
  • UrlPreview (used to display enriched url preview)
  • Giphy (used to show giphy cards)

Lets 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 line/spacer.

src/components/DateSeparator.js

Now after this, all you need to do is pass MessageSlack and DateSeparator to 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:

import streamChatTheme from ‘./src/stream-chat-theme.js’;<Chat client={chatClient} style={streamChatTheme}>    // All the child components here
</Chat>

And that’s it! You should see a 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.

Following components that we have used in InputBox are provided by Stream’s react-native sdk, which take care of plenty of things for us:

  1. AutoCompleteInput — takes care of all features of the input box such as mentions, sending messages, maintaining enabled/disabled state etc.
  2. SendButton
  3. 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, so 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.

import {InputBox} from ‘./src/components/InputBox’;<MessageInput Input={InputBox} />

The final version of the ChannelScreen component is as follows:

Please 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 below.

In the next part of the tutorial — which will be published later — we will cover additional UI components and polish, along with 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 few links to help you if you get stuck along the way:

  1. The slack-clone react-native github repo
  2. The slack-clone expo github repo
  3. Docs for react-navigation
  4. Stream’s chat component library — github repo

--

--