Create App And Install Dependencies
To get started with this tutorial, make sure you have set up your development environment for React Native. The Stream Chat React Native SDK supports apps created with the React Native CLI as well as apps created with Expo.
To get started, create an application with either the React Native CLI, or the Expo CLI and install the necessary dependencies:
12345# Initialize the app npx react-native init MyStreamChatApp # Navigate to the app directory cd MyStreamChatApp
12345# Initialize the app expo init MyStreamChatApp # Navigate to the app directory cd MyStreamChatApp
When prompted, select one of the blank templates.
Stream Chat has a number of peer dependencies that are required to take advantage of all of the out of the box features. It is suggested you follow the install instructions for each package to ensure it is properly setup. Most if not all of the required packages now support auto-linking so setup should be minimal.
1234567891011# Add Stream Chat React Native SDK yarn add stream-chat-react-native # Add the listed dependencies yarn add @react-native-camera-roll/camera-roll @react-native-community/netinfo @stream-io/flat-list-mvcp react-native-fs react-native-gesture-handler react-native-image-crop-picker react-native-image-resizer react-native-reanimated react-native-svg # Install Pods npx pod-install
1234567# Add Stream Chat Expo SDK expo install stream-chat-expo # Add the listed dependencies expo install @stream-io/flat-list-mvcp @react-native-community/netinfo expo-file-system expo-image-manipulator expo-image-picker expo-media-library react-native-gesture-handler react-native-reanimated react-native-svg
Add Optional Dependencies
File picker
Access the file picker and upload the file.
1yarn add react-native-document-picker
1yarn add expo-document-picker
Video Support
Installing this package allows you to play the video and audio files/attachments in the chat. Otherwise by default, video and audio files will be opened and downloaded through the default browser of the device.
1yarn add react-native-video
1yarn add expo-av
Haptic Feedback
Enables haptic feedback when scaling images in the image gallery if the scaling hits the higher or lower limits for its value.
1yarn add react-native-haptic-feedback
1yarn add expo-haptics
Attachment Sharing
Installing this package will allow your users to share attachments from the gallery using the native sharing interface on their devices.
1yarn add react-native-share
1yarn add expo-sharing
Copying messages
Adds ability to copy messages to the clipboard.
1yarn add @react-native-clipboard/clipboard
1yarn add expo-clipboard
The most important steps to get started are:
- Add the Babel plugin for
react-native-reanimated
to yourbabel.config.js
file in your application folder:
1234567module.exports = { ... plugins: [ ... 'react-native-reanimated/plugin', // Reanimated plugin has to be listed last. ], };
- Import
react-native-gesture-handler
at the top of yourindex.js
file. It should look as follows:
1234567import 'react-native-gesture-handler'; import { AppRegistry } from 'react-native'; import App from './App'; import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => App);
Please also follow the steps mentioned in the links below for corresponding dependencies:
react-native
- additional installation stepsreact-native-image-crop-picker
- additional installation stepsreact-native-cameraroll
- additional installation steps
Now you should be able to run the app on simulator by running following command:
1yarn ios
1yarn android
Setup Basic Navigation Stack
The Stream Chat SDK does not handle navigation, but libraries like React Navigation makes it easy for us to set up a stack navigator, and the three screens we need for our application.
Please install the following packages to get started with React Navigation, as mentioned in their documentation
1234567# Install react-navigation yarn add @react-navigation/native@^6.0.10 @react-navigation/stack@^6.2.1 react-native-screens@^3.13.1 react-native-safe-area-context@^4.2.5 # Install Pods npx pod-install
We'll set up a simple Navigation
stack to hold the necessary screens for navigation in our app,
and start with a basic HomeScreen
, which we will replace later with Chat related screens.
You can simply copy-paste following code in App.js
file.
123456789101112131415161718192021222324252627import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Text } from 'react-native'; const Stack = createStackNavigator(); const HomeScreen = () => <Text>Home Screen</Text>; const NavigationStack = () => { return ( <Stack.Navigator> <Stack.Screen name='Home' component={HomeScreen} /> </Stack.Navigator> ); }; export default () => { return ( <SafeAreaView style={{ flex: 1 }}> <NavigationContainer> <NavigationStack /> </NavigationContainer> </SafeAreaView> ); };
As mentioned in RNGH documentation,
we also need to configure GestureHandlerRootView
component.
Although react-navigation library already use GestureHandlerRootView
as a wrapper to enable gesture interactions.
So for this tutorial, you don't need this step. But if you are using a native navigation library like wix/react-native-navigation
you need to make sure that every screen is wrapped with GestureHandlerRootView
.
123456789101112import { GestureHandlerRootView } from 'react-native-gesture-handler'; ... export default () => { return ( <GestureHandlerRootView style={{ flex: 1 }}> <SafeAreaView style={{ flex: 1 }}> ... </SafeAreaView> </GestureHandlerRootView> ); };
Setup up Test Channels and Test Users (Optional)
If you have created a new app via GetStream dashboard, you will need to create few test channels and test users to see chat in action. You can easily do so from Chat Explorer. We would recommend you to create at least two three channels and three test users for nice tutorial experience.
NOTE: Don't forget to add the users as members to the channels you created.
If you prefer to create channels and users programatically, please check the documentation around Creating Channels and Creating Users.
Add Stream Chat to the application
The first thing we need to set up is the chat client, which is the low level JavaScript client
used by the SDK to communicate with the Stream Chat service.
The chat client is a JavaScript class object that we for the sake of simplicity in this tutorial will keep as a globally
accessible variable. getInstance
is a static method that takes apiKey
as parameter and returns a singleton instance of the chat client.
You can safely use getInstance
method to get the chat client instance, anywhere in your application.
This way you can avoid passing the chat client instance around through context or props.
You can find apiKey
on an GetStream dashboard for your app.
1const chatClient = StreamChat.getInstance('YOUR_API_KEY');
Let's create a separate file chatConfig.js
to store all the config values. Generally you will want to store them in environment variables.
12// chatConfig.js export const chatApiKey = 'YOUR_API_KEY',
To keep things clean, let's create a separate react hook useChatClient.js
.
This hook will be responsible for connecting the user to chat and returning a boolean flag to indicate whether the client is connected.
123456789101112131415// useChatClient.js import { useEffect, useState } from 'react'; import { StreamChat } from 'stream-chat'; import { chatApiKey } from './chatConfig'; const chatClient = StreamChat.getInstance(chatApiKey); export const useChatClient = () => { const [clientIsReady, setClientIsReady] = useState(false); return { clientIsReady, }; };
Now we can add the logic for connecting the user. We'll use the connectUser
method of the chat client to connect the user.
When we say "connect", we are basically establishing a WebSocket connection with Stream backend
so that user will start receiving WebSocket events for messages, reactions etc.
It should only be used once per session per user. Opening multiple WebSockets per user opens your application to a myriad of problems, including performance, billing, and unexpected behavior.
Please take a look at Best Practices guide for more details.
connectUser
function required two parameters:
- User object: Must have an id field. User Ids can only contain characters a-z, 0-9, and special characters
@
,_
and-
. It can have as many custom fields as you want, as long as the total size of the object is less than 5KB - User Token: The user authentication token. See Tokens & Authentication for details
In real applications, you will want to store user id in local storage and fetch the token from your backend.
But for the sake of tutorial, we will just add the userId and user token to chatConfig.js
file.
12345// chatConfig.js export const chatApiKey = 'YOUR_API_KEY'; export const chatUserId = 'USER_ID'; export const chatUserToken = 'USER_TOKEN'; export const chatUserName = 'USER_NAME';
And now we can use these configs to connect the user to chat as follows:
1234567891011121314151617181920212223242526272829303132333435363738394041424344// useChatClient.js import { useEffect, useState } from 'react'; import { StreamChat } from 'stream-chat'; import { chatApiKey, chatUserId, chatUserName, chatUserToken } from './chatConfig'; const user = { id: chatUserId, name: chatUserName, }; const chatClient = StreamChat.getInstance(chatApiKey); export const useChatClient = () => { const [clientIsReady, setClientIsReady] = useState(false); useEffect(() => { const setupClient = async () => { try { chatClient.connectUser(user, chatUserToken); setClientIsReady(true); // connectUser is an async function. So you can choose to await for it or not depending on your use case (e.g. to show custom loading indicator) // But in case you need the chat to load from offline storage first then you should render chat components // immediately after calling `connectUser()`. // BUT ITS NECESSARY TO CALL connectUser FIRST IN ANY CASE. } catch (error) { if (error instanceof Error) { console.error(`An error occurred while connecting the user: ${error.message}`); } } }; // If the chat client has a value in the field `userID`, a user is already connected // and we can skip trying to connect the user again. if (!chatClient.userID) { setupClient(); } }, []); return { clientIsReady, }; };
During the logout process, you should call the
disconnectUser
method of the chat client.
Now useChatClient
hook is ready to be integrated with our navigation stack.
We will use the clientIsReady
flag to determine whether to render the chat components or not.
1234567891011121314151617181920... import { useChatClient } from './useChatClient'; ... const NavigationStack = () => { const { clientIsReady } = useChatClient(); if (!clientIsReady) { return <Text>Loading chat ...</Text> } return ( <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> </Stack.Navigator> ); }; ...
Creating the App context
A context should ideally be created to store the details of the current Channel and the Thread as set by the user on selecting the Channel from the ChannelList or Thread from the Message list.
We will create the context AppContext
such as:
12345678910111213141516171819// AppContext.js import React, { useState } from 'react'; export const AppContext = React.createContext({ channel: null, setChannel: (channel) => {}, thread: null, setThread: (thread) => {}, }); export const AppProvider = ({ children }) => { const [channel, setChannel] = useState(); const [thread, setThread] = useState(); return <AppContext.Provider value={{ channel, setChannel, thread, setThread }}>{children}</AppContext.Provider>; }; export const useAppContext = () => React.useContext(AppContext);
To use the context we wrap the default component of the App.js
file with AppProvider
as follows:
1234567891011121314import { AppProvider } from "./AppContext"; ... export default () => { return ( <AppProvider> <GestureHandlerRootView style={{ flex: 1 }}> <SafeAreaView style={{ flex: 1 }}> ... </SafeAreaView> </GestureHandlerRootView> </AppProvider> ); };
Configure OverlayProvider Component
The OverlayProvider
is the highest level of the Stream Chat components and must be used near the root of your application (outside of any navigation stack).
The OverlayProvider
allows users to interact with messages on long press above the underlying views,
use the full screen image viewer, and use the AttachmentPicker
as a keyboard-esk view.
The OverlayProvider
can be used with no props provided but there are a plethora of props for customizing the components in the overlay.
1234567891011121314151617... import { OverlayProvider } from 'stream-chat-react-native'; // Or stream-chat-expo ... const NavigationStack = () => { ... return ( <OverlayProvider> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> </Stack.Navigator> </OverlayProvider> ); };
If you are seeing some error at this point, please refer to our troubleshooting guide
Configure Chat Component
Chat
component mainly acts as provider of chatClient to the rest of the underlying components.
It also takes care of the network connectivity, AppState handling etc.
You can choose to wrap your entire application in Chat
similar to what is required for the OverlayProvider
, or you can implement Chat
at the screen level.
Chat
has one required prop - client
, which is the instance of StreamChat you created. Also as mentioned earlier in the tutorial, we can safely
access the chatClient
instance using getInstance
method, since its a singleton.
123456789101112131415161718192021222324... import { Chat, OverlayProvider } from 'stream-chat-react-native'; // Or stream-chat-expo import { StreamChat } from 'stream-chat'; import { chatApiKey } from './chatConfig'; ... const chatClient = StreamChat.getInstance(chatApiKey); const NavigationStack = () => { ... return ( <OverlayProvider> <Chat client={chatClient}> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> </Stack.Navigator> </Chat> </OverlayProvider> ); };
Configure Channel List Component
Stream Chat for React Native provides a ChannelList
component out of the box for displaying a list of channels.
Before we configure this component, let's first set up a screen for the channel list within the existing navigation stack. We will simply rename HomeScreen to ChannelListScreen.
123-4+5+6+789101112131415-16+171819202122... const Stack = createStackNavigator(); const HomeScreen = () => <Text>Home Screen</Text>; const ChannelListScreen = () => { return null } const chatClient = StreamChat.getInstance(chatApiKey); const NavigationStack = () => { ... return ( <OverlayProvider> <Chat client={chatClient}> <Stack.Navigator> <Stack.Screen name="Home" component={HomeScreen} /> <Stack.Screen name="ChannelList" component={ChannelListScreen} /> </Stack.Navigator> </Chat> </OverlayProvider> ); };
Now we can render ChannelList
component within ChannelListScreen
.
ChannelList
can be used with no props and will return all of the channels the set user has access to.
Although in practical applications, you will probably want to only show the channels that current user is member of.
For such filtering purpose, you can provide a filters
prop to ChannelList
which will filter the channels.
If your application does not have any channels yet, you can create them via Chat Explorer on dashboard.
Additionally ChannelList
component also takes sort
prop for sorting the channels, and options
props to provide additional query options.
Please check the documentation for Querying Channels.
For more information and various use cases of filters, sort and options.
1234567891011121314151617181920212223242526import { ... ChannelList, } from 'stream-chat-react-native'; // Or stream-chat-expo import { chatApiKey, chatUserId } from './chatConfig'; ... const filters = { members: { '$in': [chatUserId] }, }; const sort = { last_message_at: -1, }; const ChannelListScreen = props => { return ( <ChannelList filters={filters} sort={sort} /> ); }; ...
Navigate From ChannelList to Channel
You can add the press handler for the list item within the ChannelList
component using a prop - onSelect
.
And this is where you can add the logic for navigating to the channel screen, where we will render the channel header, message list, input box etc.
Let's implement the basic ChannelScreen
component and logic for navigating from ChannelList to Channel Screen.
12345678910111213141516171819202122232425import { useAppContext } from ./AppContext; ... const ChannelScreen = props => { return null; } const ChannelListScreen = props => { const { setChannel } = useAppContext(); return ( <ChannelList onSelect={(channel) => { const { navigation } = props; setChannel(channel); navigation.navigate('ChannelScreen'); }} ... /> ); }; ... <Stack.Navigator> <Stack.Screen name="ChannelListScreen" component={ChannelListScreen} /> <Stack.Screen name="ChannelScreen" component={ChannelScreen} /> </Stack.Navigator>
Configure Channel Component
The channel screen will comprise three main components:
MessageList
component used to render the list of messages sent in a channel.MessageInput
component used to render the input box needed to send messages, images, files, and commands to a channel.Channel
component that holds all data related to a channel. It also acts as a bridge between MessageList and MessageInput component.
The Channel
component takes the channel
as a prop. The MessageList
and MessageInput
components don't need any props to be set, and we'll use the defaults set for these components.
12+3+4+5678910+1112-13+14+15+16+17+18+1920import { ... Channel, MessageList, MessageInput, } from 'stream-chat-react-native'; // Or stream-chat-expo ... const ChannelScreen = props => { const { channel } = useAppContext(); return null; return ( <Channel channel={channel}> <MessageList /> <MessageInput /> </Channel> ); };
At this point, you should be able to make use of various chat features like sending messages, uploading files, sending images, sending commands etc. as shown below.
Navigate from Channel to Thread Screen
Threads are a feature that allows you to start a conversation on a particular message in a message list, similar to what Slack offers.
Let's first setup a separate screen for thread within our navigation stack.
12345678910... const ThreadScreen = props => { return null; } <Stack.Navigator> <Stack.Screen name="ChannelListScreen" component={ChannelListScreen} /> <Stack.Screen name="ChannelScreen" component={ChannelScreen} /> <Stack.Screen name="ThreadScreen" component={ThreadScreen} /> </Stack.Navigator>
As explained in the previous section, when a user long presses on a message, it opens an overlay
where the user can add a reaction and also can see a bunch of actions for the message.
MessageList
component accepts a prop function onThreadSelect
which gets called when a user
selects "Thread Reply" action on the message overlay.
123456789101112131415161718const ChannelScreen = (props) => { const { navigation } = props; const { channel, setThread } = useAppContext(); return ( <Channel channel={channel}> <MessageList onThreadSelect={(message) => { if (channel?.id) { setThread(message); navigation.navigate('ThreadScreen'); } }} /> <MessageInput /> </Channel> ); };
You can now long press on a message and select "Thread Reply" action to open thread screen, which we will configure in the next step.
Configure Thread Screen
React Native Chat SDK provides a Thread
component
to easily configure thread screen for a message. This component needs to be wrapped inside Channel
component with a boolean prop
threadList
set to true.
This way, the Channel
component is aware that it is being rendered within a thread screen and can avoid
concurrency issues.
12+345678+910-11+12+13+14+15+1617import { ... Thread, } from 'stream-chat-react-native'; // Or stream-chat-expo ... const ThreadScreen = props => { const { channel, thread } = useAppContext(); return null; return ( <Channel channel={channel} thread={thread} threadList> <Thread /> </Channel> ); }
Enable Offline Support
Chat RN SDK offers offline support OOTB. Offline support improves the load time of app and helps improve the user experience in case of slow network. Please read more about offline support in our documentation.
Install Sqlite Library
12yarn add react-native-quick-sqlite npx pod-install
Add enableOfflineSupport
prop
Add enableOfflineSupport
prop on Chat
component
1234... <Chat client={chatClient} enableOfflineSupport> ... </Chat>
If you run the app, you should be able to load the chat without any network.
How Can You Customize Chat UI
Until now we have concluded the basic setup of chat within the application.
Although every application has a different UI and UX requirements, and the default designs are not always suitable for your application. Stream's React Native Chat is designed to be flexible and easily customizable.
Every underlying component within the chat SDK can be customized by passing a custom component as a prop to one of the core components, such as ChannelList
, Channel
or OverlayProvider
depending on who the parent of that UI component is. This way, you can either fully customize the necessary UI component or wrap the default component
with a custom view.
You will have access to necessary data within your custom component via props and context. To access information from these contexts we suggest using the hooks that are provided by the library. Following table lists some of the contexts provided by library and corresponding hooks for accessing them. You can find list of all the contexts and hooks on Contexts documentation.
Context | Hook | Provider Component |
---|---|---|
AttachmentPickerContext | useAttachmentPickerContext | OverlayProvider |
ChannelContext | useChannelContext | Channel |
ChannelsContext | useChannelsContext | ChannelList |
... | ... | ... |
We will cover the usage of context in the next section where we customize the message list.
Additionally you can also style the default component by simply providing a theme object containing custom styles.
We have demonstrated the power of Stream Chat React Native SDK by building open source clones of some popular chat applications such as Whatsapp, Slack and iMessage. Source code for all these projects is available under react-native-samples repository.
In the following sections, we will walk through some examples that will cover the basics around customizations and theming.
Customize ChannelList
ChannelList
is a FlatList
of channels.
To customize the channel list item, you can pass a prop Preview
to ChannelList
component.
Default value of the Preview
prop is ChannelPreviewMessenger
component,
basically the default UI component.
Objective: Add a light blue background for unread channels.
Let's start by creating a custom component for the list item, which simply returns the default UI component ChannelPreviewMessenger
.
123456789101112131415161718import { ... ChannelPreviewMessenger } from 'stream-chat-react-native'; // Or stream-chat-expo const CustomListItem = props => { return ( <ChannelPreviewMessenger {...props} /> ) } ... <ChannelList Preview={CustomListItem} filters={filters} ... />
Unread count on channel can be accessed via unread
prop.
We will use this count to conditionally add a light blue background for unread channels.
12345678910const CustomListItem = (props) => { const { unread } = props; const backgroundColor = unread ? '#e6f7ff' : '#fff'; return ( <View style={{ backgroundColor }}> <ChannelPreviewMessenger {...props} /> </View> ); };
You won't see any background color change for unread channels yet, since ChannelPreviewMessenger
has a white background by default.
Thus, we will need to first override the default background color to transparent
to make the wrapped view background visible.
To achieve this, we will use the theming system provided by the stream-chat-react-native
library.
You can find all the themeable properties in theme.ts file.
123456789101112131415161718... const chatTheme = { channelPreview: { container: { backgroundColor: 'transparent', } } }; const NavigationStack = () => { ... return ( <OverlayProvider value={{ style: chatTheme }}> ... </OverlayProvider> ); };
Similarly, along with of customizing the entire list item component, you can also customize the individual components within the list item.
E.g., PreviewStatus
, PreviewAvatar
, PreviewMessage
etc.
You can use the visual guide to find out which components you can customize.
Customize Message List
Every component within MessageList and MessageInput can be customized by passing a custom component as a prop to Channel component.
Objective: Replace default message UI with custom component
The most common use case of customizing the MessageList
is to have a custom UI for the message.
You can do so by providing a prop MessageSimple
to Channel
component as shown below.
1234567891011... const CustomMessage = () => { return null; } <Channel channel={channel} MessageSimple={CustomMessage} ... />
Now that we have configured the component, let's render the text of the message on the UI.
You can access the message
object from the MessageContext
context. MessageContext
also gives you access to a boolean isMyMessage
which
we can use to style the message UI conditionally.
You can also access plenty of other useful properties and default call to action handlers from this context such as
handleDeleteMessage
,handleResendMessage
,onLongPress
etc. Please check the API documentation for MessageContext for the full list.
12+345678+910-11+12+13+14+15+16+17+18+19+20+21+22+2324import { ... useMessageContext, } from 'stream-chat-react-native'; // Or stream-chat-expo ... const CustomMessage = () => { const { message, isMyMessage } = useMessageContext(); return null; return ( <View style={{ alignSelf: isMyMessage ? 'flex-end' : 'flex-start', backgroundColor: isMyMessage ? '#ADD8E6' : '#ededed', padding: 10, margin: 10, borderRadius: 10, width: '70%', }}> <Text>{message.text}</Text> </View> ) }
This is a really simplified version of custom message UI which only displays text.
You can obviously add required functionality such as onPress
, onLongPress
handlers etc according to your needs.
But generally, you wouldn't need to customize the entire message UI, but only the required parts such as MessageStatus
, MessageAvatar
etc.
For this purpose, you can check the visual guide
to decide which prop to pass to the Channel
component. You can access MessageContext
at every message level component.
Also, we would recommend you to check the following guides for a bit more advanced customizations:
- Message Customization guide
- Custom Attachments
- Custom Message Actions
- Custom Message Input Box
- MessageList For Livestream Application
Conclusion
That concludes the customization section for React Native Chat SDK. Now you should have a good overview of how to do a basic setup around chat components, and customize them as per your design/UX requirements. We have covered only the basic things, but the possibilities are endless.
Final Thoughts
In this chat app tutorial we built a fully functioning React Native messaging app with our React Native SDK component library. We also showed how easy it is to customize the behavior and the style of the React Native chat app components with minimal code changes.
Both the chat SDK for React Native and the API have plenty more features available to support more advanced use-cases such as push notifications, content moderation, rich messages and more. Please check out our Android tutorial too. If you want some inspiration for your app, download our free chat interface UI kit.