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

Vishal N.
Vishal N.
Published November 17, 2020 Updated March 11, 2021

Note: This blog is archived due to limited compatibility with an old version of the React Native chat SDK. Please check our latest tutorial or our finished Slack clone.

In Part 2 of this tutorial, we covered how to build Slack-like navigation, channel list screen, channel screen, reaction picker, and action sheet. In this tutorial, Part 3, we will build various search screens and thread screen.

Resources πŸ‘‡

Below are a few helpful links if you get stuck along the way:

Thread Screen

  • The MessageList component accepts the prop function onThreadSelect, which is attached to the onPress handler for reply count text below the message bubble. If you check our ChannelScreen component, you will see navigation logic to ThreadScreen added to the onThreadSelect prop on the MesaageList component.

  • Thread is provided out-of-the-box from stream-chat-react-native. If you look at the source code, it's a set of Message (parent message bubble), MessageList, and a MessageInput component. You can customize these underlying components using props – additionalParentMessageProps, additionalMessageListProps and additionalMessageInputProps. We can use this Thread component easily for our purpose.

  • We need to implement a checkbox labeled "Also send to {channel_name}" (as shown in the screenshot below). When ticked, the message should appear on the channel as well. We can use show_in_channel property on the message object for this, as mentioned in docs for threads and replies

If you specify show_in_channel, the message will be visible both in a thread of replies and the main channel.

Thread Reply in the Channel

If the checkbox is ticked, add show_in_channel: true to the message object before sending it. We can achieve this by providing a doSendMessageRequest prop function, which overrides Channel components default sendMessage handler.

Input box for thread

Use the Animated API by React Native to achieve the sliding animation of the checkbox and other action buttons.

// src/components/InpuBoxThread.js

import React, {useRef, useState} from 'react';
import {TouchableOpacity, Animated, View, StyleSheet} from 'react-native';
import {
} from 'stream-chat-react-native';
import {getChannelDisplayName} from '../utils';

import {useTheme} from '@react-navigation/native';
import {SVGIcon} from './SVGIcon';
import CheckBox from '@react-native-community/checkbox';
import {SCText} from './SCText';

Now assign the ThreadScreen component to its respective HomeStack.Screen in App.js.

import { ThreadScreen } from './src/screens/ThreadScreen';
    options={{headerShown: false}}

Search Screens

There are four modal search screens that we are going to implement in this tutorial:

Slack Clone Layout

Jump to Channel Screen & Channel Search Screen

We can create a standard component for Jump to channel screen and Channel search screen.

Let's first create a common component needed across the search screens.

Direct Messaging Avatar

This is a component for the avatar of direct messaging conversation:

  • For one to one conversations, it shows other member's picture with his presence indicator
  • For group conversation, it shows stacked avatars of two of its members.
Status Indicators
// src/components/DirectMessagingConversationAvatar.js

import React from 'react';
import {View, StyleSheet, Image} from 'react-native';

import {ChatClientService} from '../utils';
import {useTheme} from '@react-navigation/native';
import {PresenceIndicator} from './ChannelListItem';

export const DirectMessagingConversationAvatar = ({channel}) => {
  const chatClient = ChatClientService.getClient();
  const {colors} = useTheme();
  const otherMembers = Object.values(channel.state.members).filter(
    m => m.user.id !== chatClient.user.id,

This is a common header for modal screens, with a close button on the left and title in the center.

// src/components/ModalScreenHeader.js

import React from 'react';
import {TouchableOpacity, View, Text, Image, StyleSheet} from 'react-native';
import {useTheme} from '@react-navigation/native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {SCText} from './SCText';

export const ModalScreenHeader = ({goBack, title, subTitle}) => {
  const {colors} = useTheme();
  const insets = useSafeAreaInsets();

  return (

Now let's build a ChannelSearchScreen, which can be used as "Jump to channel screen" and "Channel search." There are two main differences between these screens, which we will control through a prop β€” channelsOnly.

  1. "Jump to channel screen" doesn't have a header
  2. "Channel search screen" doesn't have a horizontal list of recent direct messaging conversation members.

Also, we need to display a list of recent conversations when the user opens this modal. We can use the cached list of recent conversations in CacheService (which we populated in the ChannelList component via the useWatchedChannels hook) to avoid extra calls to the queryChannels API endpoint.

// src/screens/ChannelSearchScreen.js

import React, {useState} from 'react';
import {
} from 'react-native';
import {useNavigation, useRoute, useTheme} from '@react-navigation/native';
import debounce from 'lodash/debounce';

import {CacheService, ChatClientService} from '../utils';

Assign the ChannelSearchScreen component to its respective ModalStack.Screen in App.js.

import {ChannelSearchScreen} from './src/screens/ChannelSearchScreen';
    options={{headerShown: false}}

New Message Screen

Highlights of this screen (NewMessageScreen) are as following:

  1. Inputbox on top is a multi-select input. One can select multiple users there. This can be quickly built as a separate component β€” UserSearch. Expose onChangeTags callback as a prop function to give parent component access to selected users.
  2. UserSearch component uses queryUsers endpoint provided available on chat client. Please check docs for
  3. When the user focuses on the input box at the bottom of the screen, the app should create a conversation between the already selected users in the top (UserSearch) input box. We handle this in the onFocus handler for the input box at the bottom of the screen.
// src/screens/NewMessageScreen.js

import React, {useEffect, useState} from 'react';
import {View, SafeAreaView, StyleSheet} from 'react-native';
import {
} from 'stream-chat-react-native';
import {useTheme} from '@react-navigation/native';

import {DateSeparator} from '../components/DateSeparator';
import {InputBox} from '../components/InputBox';
import {MessageSlack} from '../components/MessageSlack';

Now assign the NewMessageScreen component to its respective ModalStack.Screen in App.js.

import {NewMessageScreen} from './src/screens/NewMessageScreen';
    options={{headerShown: false}}

Message Search Screen

We are going to implement a global search for message text on this screen β€” MessageSearchScreen.

Note: The official Slack app provides richer features such as search in a specific channel or search by attachments. Here, we are keeping it limited to a global search, although channel-specific search is also possible using Stream Search API

  1. Global message search is relatively heavy for the backend so that search won't happen onChangeText, but when the user presses the search button explicitly. TextInput component has returnKeyType prop which we need for our use case.
  2. Component uses search endpoint available on chat clients. Please check docs for message endpoint
  3. Search results display a list of messages; when pressed, they should go to the channel screen on that particular message. We are going to build a separate screen for this β€” TargettedMessageChannelScreen. This component is quite similar to ChannelScreen, but it queries the channel at a specific message (provided through props) instead of the latest message as follows:
const channel = chatClient.channel("messaging", message.channel.id);
const res = await channel.query({
 messages: { limit: 10, id_lte: message.id },

// We are tricking Channel component from stream-chat-react-native into believing
// that provided channel is initialized, so that it doesn't call .watch() on channel.
channel.initialied = true;

// And then use channel in Channel component
<Channel channel={channel}>...</Channel>;
  1. When the user lands on this screen, he can see the list of past searches. Store every search text in AsyncStorage.

Copy the following components in your app:

// src/screens/MessageSearchScreen.js

import React, {useEffect, useRef, useState} from 'react';
import {View, StyleSheet} from 'react-native';
import {
} from 'react-native';
import {

Assign the MessageSearchScreen and TargettedMessageChannelScreen component to its respective ModalStack.Screen in App.js.

import { MessageSearchScreen } from './src/screens/MessageSearchScreen';
import { TargettedMessageChannelScreen } from './src/screens/TargettedMessageChannelScreen';
      options={{headerShown: false}}
      options={{headerShown: false}}

Implementation for additional more screens (shown in screenshots below) is available in slack-clone-react-native repository. If you managed to follow the tutorial so far, implementation of following screens should be easy to understand.

Congratulations! πŸ‘

Chat Slack Clone

You've completed Part 3, the final step, of our tutorial on building a Slack clone using the Stream’s Chat API with React Native. I hope you found this tutorial helpful!

Happy coding!

decorative lines
Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->