Learn how to quickly integrate rich Generative AI experiences directly into Stream Chat. Learn More ->

React Native Chat App Expo Tutorial

Build a fully functioning messaging app using Stream’s React Native SDK component library. By the end of this tutorial, you’ll have a mobile app that you can easily customize in both behavior and style with minimal code changes.

example of react chat sdk

Create App And Install Dependencies

To get started with this tutorial, make sure you have set up your development environment for React Native.

To get started, create an application with the Expo CLI and install the necessary dependencies:

bash
1
2
3
4
5
# Initialize the app with the Expo CLI npx create-expo-app MyStreamChatApp --template blank # Navigate to the app directory cd MyStreamChatApp

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.

bash
1
2
3
# Add Stream Chat Expo SDK npx expo install stream-chat-expo
bash
1
2
3
# Add the listed dependencies npx expo install @stream-io/flat-list-mvcp @react-native-community/netinfo expo-file-system expo-image-manipulator react-native-gesture-handler react-native-reanimated react-native-svg

Add Optional Dependencies

Image Media Library Picker

Access and upload image using an inbuilt image picker in the SDK.

bash
1
npx expo install expo-media-library

Native Image Picker

Access and upload image using an native image picker.

bash
1
npx expo install expo-image-picker

File picker

Access the file picker and upload the file.

bash
1
npx expo install expo-document-picker

Camera Picker

Access the camera and upload the image.

bash
1
npx expo install expo-image-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.

bash
1
npx expo install 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.

bash
1
npx expo install 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.

bash
1
npx expo install expo-sharing

Copying messages

Adds ability to copy messages to the clipboard.

bash
1
npx expo install expo-clipboard

Audio Recording and playback

Adds ability to record and play audio messages.

bash
1
npx expo install expo-av

Offline support

Adds offline support to the chat application.

bash
1
npx expo install react-native-quick-sqlite

Application level setup

The most important steps to get started are:

js
1
2
3
4
5
6
7
module.exports = { ... plugins: [ ... 'react-native-reanimated/plugin', // Reanimated plugin has to be listed last. ], };
  • Import react-native-gesture-handler at the top of your project(say _layout.js, if you use expo-router). It should look as follows:
js
1
2
3
4
5
6
7
import "react-native-gesture-handler"; import { AppRegistry } from "react-native"; import App from "./App"; import { name as appName } from "./app.json"; AppRegistry.registerComponent(appName, () => App);

Now you should be able to run the app on simulator by running following command:

iOS
Android
bash
1
yarn run ios # or npx react-native run-ios

Setup Basic Navigation

The Stream Chat SDK does not handle navigation, but we can use expo-router to setup basic navigation for the app.

Please install the following packages to get started with Expo Router, as mentioned in their documentation.

bash
1
2
# Install expo-router and other dependencies npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar

After this step, please follow the steps mentioned in the Expo Router documentation to setup the basic navigation for the app. This involves crucial steps like setting up the entry point, adding scheme for deep linking and adding babel config.

We'll set up a simple stack navigation 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.

To do that, you can use the Stack component from expo-router in your app/_layout.js file, as follows:

app/_layout.js (jsx)
1
2
3
4
5
6
import "react-native-gesture-handler"; import { Stack } from "expo-router"; export default function App() { return <Stack />; }

We will then create an index.js file as below and this will be the entry screen for our app.

app/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { StatusBar } from "expo-status-bar"; import { StyleSheet, Text, View } from "react-native"; export default function Home() { return ( <View style={styles.container}> <Text>Home Screen</Text> <StatusBar style="auto" /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: "center", justifyContent: "center", }, });

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.

app/_layout.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { StyleSheet } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { Stack } from "expo-router"; export default () => { return ( <SafeAreaProvider> <GestureHandlerRootView style={styles.container}> <Stack /> </GestureHandlerRootView> </SafeAreaProvider> ); }; const styles = StyleSheet.create({ container: { flex: 1, }, });

Add Stream Chat to the application

Before we begin working with the chat UI components we'll need to set up a StreamChat client which abstracts API calls into methods, handles state and real-time events. To make the instantiation and connection handling easier, we've prepared a simple hook (useCreateChatClient) that you can use in your application.

You can find apiKey on an GetStream dashboard for your app. You can create token for authentication using this link for a specific user id.

chatConfig.js (js)
1
2
3
4
export const chatApiKey = "REPLACE_WITH_API_KEY"; export const chatUserId = "REPLACE_WITH_USER_ID"; export const chatUserName = "REPLACE_WITH_USER_NAME"; export const chatUserToken = "REPLACE_WITH_USER_TOKEN";

And now we can use these configs to create a client and connect the user to chat as follows:

components/ChatWrapper.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from "react"; import { Text } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { useCreateChatClient } from "stream-chat-expo"; import { chatApiKey, chatUserId, chatUserName, chatUserToken, } from "../chatConfig"; const user = { id: chatUserId, name: chatUserName, }; export const ChatWrapper = ({ children }) => { const chatClient = useCreateChatClient({ apiKey: chatApiKey, userData: user, tokenOrProvider: chatUserToken, }); if (!chatClient) { return ( <SafeAreaView> <Text>Loading chat ...</Text> </SafeAreaView> ); } return <>{children}</>; };

Note: Make sure you use the useCreateChatClient hook only once per application. If you need the client instance somewhere down in the component tree use the useChatContext hook (exported by the stream-chat-react) to access it.

To use the connected client, we wrap the content of the app/_layout.js with the above ChatWrapper:

app/_layout.js (js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { StyleSheet } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { ChatWrapper } from "../components/ChatWrapper"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { Stack } from "expo-router"; ... export default function Layout() { return ( <SafeAreaProvider> <GestureHandlerRootView style={styles.container}> <ChatWrapper> <Stack /> </ChatWrapper> </GestureHandlerRootView> </SafeAreaProvider> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });

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 MessageList. We will create the context AppContext such as:

contexts/AppContext.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 _layout.js file with AppProvider as follows:

app/_layout.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { StyleSheet } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { SafeAreaProvider } from "react-native-safe-area-context"; import { Stack } from "expo-router"; import { ChatWrapper } from "../components/ChatWrapper"; import { AppProvider } from "../contexts/AppContext"; ... export default function Layout() { return ( <SafeAreaProvider> <GestureHandlerRootView style={styles.container}> <ChatWrapper> <AppProvider> <Stack /> </AppProvider> </ChatWrapper> </GestureHandlerRootView> </SafeAreaProvider> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });

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.

components/ChatWrapper.js (js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
... import { OverlayProvider } from 'stream-chat-expo'; export const ChatWrapper = ({ children }) => { ... if (!chatClient) { return ( <SafeAreaView> <Text>Loading chat ...</Text> </SafeAreaView> ); } return <OverlayProvider>{children}</OverlayProvider>; };

If you are seeing some error at this point, please refer to our troubleshooting guide.

Configure Chat Component

The 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. The 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.

components/ChatWrapper.js (js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
... import { Chat, OverlayProvider, useCreateChatClient } from 'stream-chat-expo'; import { chatApiKey, chatUserId, chatUserName, chatUserToken } from '../chatConfig'; const user = { id: chatUserId, name: chatUserName, }; export const ChatWrapper = ({ children }) => { const chatClient = useCreateChatClient({ apiKey: chatApiKey, userData: user, tokenOrProvider: chatUserToken, }); if (!chatClient) { return ( <SafeAreaView> <Text>Loading chat ...</Text> </SafeAreaView> ); } return ( <OverlayProvider> <Chat client={chatClient}>{children}</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 replace Home component in the app/index.js.

app/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { StyleSheet, View } from "react-native"; import { Stack } from "expo-router"; export default function ChannelListScreen() { return ( <View style={styles.container}> <Stack.Screen options={{ title: "Channel List Screen" }} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });

Now we can render ChannelList component within ChannelListScreen.

The 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.

app/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { StyleSheet, View } from "react-native"; import { ChannelList } from "stream-chat-expo"; import { useMemo } from "react"; import { Stack } from "expo-router"; import { chatUserId } from "../chatConfig"; const filters = { members: { $in: [chatUserId] }, type: "messaging", }; const sort = { last_updated: -1 }; const options = { state: true, watch: true, }; export default function ChannelListScreen() { const memoizedFilters = useMemo(() => filters, []); return ( <View style={styles.container}> <Stack.Screen options={{ title: "Channel List Screen" }} /> <ChannelList filters={memoizedFilters} options={options} sort={sort} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });

You can add the press handler for the list item within the ChannelList component using a prop - onSelect.

You can navigate to the channel screen using the useRouter hook of the expo-router:

app/index.js (js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useContext, useMemo } from 'react'; import { Stack, useRouter } from 'expo-router'; import { AppContext } from '../contexts/AppContext'; export default function ChannelListScreen() { ... const router = useRouter(); const { setChannel } = useContext(AppContext); return ( <View style={styles.container}> <Stack.Screen options={{ title: 'Channel List Screen' }} /> <ChannelList ... onSelect={(channel) => { setChannel(channel); router.push(`/channel/${channel.cid}`); }} /> </View> ); }

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 ChannelScreen.

To add a channel screen, create a new directory channel/[cid] under the app directory with a file name index.js. The directory structure should look like app/channel/[cid]/index.js. The cid is the unique identifier of the channel that we use while routing.

app/channel/[cid]/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { StyleSheet, View } from "react-native"; import { Stack } from "expo-router"; export default function ChannelScreen() { return ( <View style={styles.container}> <Stack.Screen options={{ title: "Channel Screen" }} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });

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 the MessageList and MessageInput components.

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.

app/channel/[cid]/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import React, { useContext, useEffect } from "react"; import { SafeAreaView, Text, View } from "react-native"; import { Channel, MessageInput, MessageList, useAttachmentPickerContext, } from "stream-chat-expo"; import { Stack } from "expo-router"; import { AppContext } from "../../../contexts/AppContext"; import { useHeaderHeight } from "@react-navigation/elements"; export default function ChannelScreen() { const { channel } = useContext(AppContext); const { setTopInset } = useAttachmentPickerContext(); const headerHeight = useHeaderHeight(); useEffect(() => { setTopInset(headerHeight); }, [headerHeight, setTopInset]); if (!channel) { return ( <SafeAreaView> <Text>Loading chat ...</Text> </SafeAreaView> ); } return ( <SafeAreaView style={{ flex: 1 }}> <Stack.Screen options={{ title: "Channel Screen" }} /> {channel ? ( <Channel channel={channel} keyboardVerticalOffset={headerHeight}> <MessageList /> <MessageInput /> </Channel> ) : null} </SafeAreaView> ); }

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.

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.

To add a thread screen, create a new directory thread/[cid] under the channel/[cid] directory with a file name index.js. The directory structure should look like app/channel/[cid]/thread/[cid]/index.js. The cid is the unique identifier of the thread that we use while routing.

app/channel/[cid]/thread/[cid]/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { StyleSheet, View } from "react-native"; import { Stack } from "expo-router"; export default function ThreadScreen() { return ( <View style={styles.container}> <Stack.Screen options={{ title: "Thread Screen" }} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, }, });

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. The MessageList component accepts a prop function onThreadSelect which gets called when a user selects "Thread Reply" action on the message overlay.

You can navigate to the thread screen using the useRouter hook of the expo-router:

app/channel/[cid]/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { useContext, useMemo } from 'react'; import { Stack, useRouter } from 'expo-router'; import { AppContext } from '../contexts/AppContext'; ... export default function ChannelScreen() { const router = useRouter(); const { setThread, channel } = useContext(AppContext); ... return ( ... <Channel channel={channel} keyboardVerticalOffset={headerHeight}> <MessageList onThreadSelect={(thread) => { setThread(thread); router.push(`/channel/${channel.cid}/thread/${thread.cid}`); }} /> <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.

app/channel/[cid]/thread/[cid]/index.js (jsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React, { useContext } from "react"; import { SafeAreaView, Text, View } from "react-native"; import { Channel, Thread } from "stream-chat-expo"; import { Stack } from "expo-router"; import { AppContext } from "../../../../../contexts/AppContext"; import { useHeaderHeight } from "@react-navigation/elements"; export default function ThreadScreen() { const { channel, thread, setThread } = useContext(AppContext); const headerHeight = useHeaderHeight(); if (channel === undefined) { return ( <SafeAreaView> <Text>Loading chat ...</Text> </SafeAreaView> ); } return ( <SafeAreaView> <Stack.Screen options={{ title: "Thread Screen" }} /> <Channel channel={channel} keyboardVerticalOffset={headerHeight} thread={thread} threadList > <View style={{ flex: 1, justifyContent: "flex-start", }} > <Thread onThreadDismount={() => { setThread(undefined); }} /> </View> </Channel> </SafeAreaView> ); }

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

bash
1
2
yarn add react-native-quick-sqlite # or npm install react-native-quick-sqlite npx pod-install

Add enableOfflineSupport prop

Add enableOfflineSupport prop on Chat component

jsx
1
2
3
4
... <Chat client={chatClient} enableOfflineSupport> ... </Chat>

Add async audio Support

To enable async audio support, you need to install the package as mentioned in the installation steps and enable it by adding the audioRecordingEnabled prop on the Chat component.

jsx
1
2
3
4
... <Channel channel={channel} audioRecordingEnabled> ... </Channel>

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.

ContextHook

Provider Component

AttachmentPickerContext

useAttachmentPickerContext

OverlayProvider
ChannelContextuseChannelContextChannel
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.

jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { ... 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.

jsx
1
2
3
4
5
6
7
8
9
10
const 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.

jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
... 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.

jsx
1
2
3
4
5
6
7
8
9
10
11
... 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.

jsx
1
2
+
3
4
5
6
7
8
+
9
10
-
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
24
import { ... 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:

Conclusion

That concludes the customization section for the 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 and iOS tutorial too. If you want some inspiration for your app, download our free chat interface UI kit.

Give us feedback!

Did you find this tutorial helpful in getting you up and running with your project? Either good or bad, we're looking for your honest feedback so we can improve.

Start coding for free

No credit card required.
If you're interested in a custom plan or have any questions, please contact us.