-
Notifications
You must be signed in to change notification settings - Fork 326
Tutorial v3.0
🔴 This tutorial is deprecated, please check our new tutorial 🔴
Make sure that
- you have a recent version of Node (10+) installed. If you are not sure, just type this in your terminal
node --version
- you have setup the environment for react-native as mentioned here - https://reactnative.dev/docs/environment-setup
To make it easier for you to follow tutorial, we have setup a repository with all the setup necessary to get you started. So lets start by cloning the repository:
git clone https://github.com/GetStream/react-native-chat-tutorial.git
cd react-native-chat-tutorial
yarn
npx pod-install
To get all the chat functionality in this tutorial, you will need to get a free 4 weeks trial of Chat. No credit card is required.
Stream Chat comes with fully functional UI components and makes it very simple to add chat to your mobile app. Let’s start by adding a simple conversation chat screen.
Open App.js in your text editor of choice and make the following changes:
import React, {useEffect, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {StreamChat} from 'stream-chat';
import {
Channel,
Chat,
MessageInput,
MessageList,
OverlayProvider as ChatOverlayProvider,
} from 'stream-chat-react-native';
import {
SafeAreaProvider,
SafeAreaView,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
const userToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c';
const user = {
id: 'ron',
};
const chatClient = StreamChat.getInstance('q95x9hkbyd6p');
const connectUserPromise = chatClient.connectUser(user, userToken);
const channel = chatClient.channel('messaging', 'channel_id');
const ChannelScreen = () => {
const {bottom} = useSafeAreaInsets();
return (
<ChatOverlayProvider bottomInset={bottom} topInset={0}>
<SafeAreaView>
<Chat client={chatClient}>
{/* Setting keyboardVerticalOffset as 0, since we don't have any header yet */}
<Channel channel={channel} keyboardVerticalOffset={0}>
<View style={StyleSheet.absoluteFill}>
<MessageList />
<MessageInput />
</View>
</Channel>
</Chat>
</SafeAreaView>
</ChatOverlayProvider>
);
};
export default function App() {
const [ready, setReady] = useState();
useEffect(() => {
const initChat = async () => {
await connectUserPromise;
await channel.watch();
setReady(true);
};
initChat();
}, []);
if (!ready) {
return null;
}
return (
<SafeAreaProvider>
<ChannelScreen channel={channel} />
</SafeAreaProvider>
);
}
With this code we know have a fully working chat mobile app running. The Chat
component is responsible of handling API calls and keep a consistent shared state across all other children components.
react-native run-ios
This will start the React Native development server, you can leave it running, it will live reload your application when you make code changes.
Chat UI React Native components come with batteries included:
The built-in MessageList
and MessageInput
components provide several rich interactions out of the box
Try copy/paste https://goo.gl/Hok8hp in a message.
Built-in user mention and autocomplete in all your chat channels
Built-in chat commands like /giphy and custom commands allow you to create rich user experiences.
Upload images directly from your Camera Roll.
Most chat applications handle more than just one single conversation. Apps like Facebook Messenger, Whatsapp and Telegram allows you to have multiple one to one and group conversations.
Let’s find out how we can change our application chat screen to display the list of conversations and navigate between them.
First of all we need to add some basic navigation to our mobile app. We want to list all conversations and be able to go from one to another. Stacked navigation can handle this very well and is supported by the awesome react-navigation package that we installed earlier on.
In order to keep things easy to follow we are going to have all code App.js
/* eslint-disable react/display-name */
/* eslint-disable react/display-name */
import React, {useContext, useEffect, useMemo, useState} from 'react';
import {LogBox, SafeAreaView, StyleSheet, View} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator, useHeaderHeight} from '@react-navigation/stack';
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import {StreamChat} from 'stream-chat';
import {
Channel,
ChannelList,
Chat,
MessageInput,
MessageList,
OverlayProvider,
useAttachmentPickerContext,
} from 'stream-chat-react-native';
LogBox.ignoreAllLogs(true);
const chatClient = StreamChat.getInstance('q95x9hkbyd6p');
const userToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c';
const user = {
id: 'ron',
};
const filters = {
example: 'example-apps',
members: {$in: ['ron']},
type: 'messaging',
};
const sort = {last_message_at: -1};
const ChannelListScreen = ({navigation}) => {
const {setChannel} = useContext(AppContext);
const memoizedFilters = useMemo(() => filters, []);
return (
<Chat client={chatClient}>
<View style={StyleSheet.absoluteFill}>
<ChannelList
filters={memoizedFilters}
onSelect={(channel) => {
setChannel(channel);
navigation.navigate('Channel');
}}
sort={sort}
/>
</View>
</Chat>
);
};
const ChannelScreen = ({navigation}) => {
const {channel} = useContext(AppContext);
const headerHeight = useHeaderHeight();
const {setTopInset} = useAttachmentPickerContext();
useEffect(() => {
setTopInset(headerHeight);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [headerHeight]);
return (
<SafeAreaView>
<Chat client={chatClient}>
<Channel channel={channel} keyboardVerticalOffset={headerHeight}>
<View style={StyleSheet.absoluteFill}>
<MessageList />
<MessageInput />
</View>
</Channel>
</Chat>
</SafeAreaView>
);
};
const Stack = createStackNavigator();
const AppContext = React.createContext();
const App = () => {
const {bottom} = useSafeAreaInsets();
const [channel, setChannel] = useState();
const [clientReady, setClientReady] = useState(false);
useEffect(() => {
const setupClient = async () => {
await chatClient.connectUser(user, userToken);
setClientReady(true);
};
setupClient();
}, []);
return (
<NavigationContainer>
<AppContext.Provider value={{channel, setChannel}}>
<OverlayProvider bottomInset={bottom}>
{clientReady && (
<Stack.Navigator
initialRouteName="ChannelList"
screenOptions={{
headerTitleStyle: {alignSelf: 'center', fontWeight: 'bold'},
}}>
<Stack.Screen
component={ChannelScreen}
name="Channel"
options={() => ({
headerBackTitle: 'Back',
headerRight: () => <></>,
headerTitle: channel?.data?.name,
})}
/>
<Stack.Screen
component={ChannelListScreen}
name="ChannelList"
options={{headerTitle: 'Channel List'}}
/>
</Stack.Navigator>
)}
</OverlayProvider>
</AppContext.Provider>
</NavigationContainer>
);
};
export default () => {
return (
<SafeAreaProvider>
<App />
</SafeAreaProvider>
);
};
If you run your application now, you will see the first chat screen now shows a list of conversations, you can open each by tapping and go back to the list.
The ChannelList
component retrieves the list of channels based on a custom query and ordering. In this case we are showing the list of channels the current user is a member and we order them based on the time they had a new message. ChannelList handles pagination and updates automatically out of the box when new channels are created or when a new message is added to a channel.
Note: you can also specify more complex queries to match your use cases. The filter prop accepts a MongoDB-like query.
Let’s see how we can change the channel preview of the ChannelList. We are going to add a small badge showing the count of unread messages for each channel.
The React Native Chat SDK library allows you to swap components easily without adding much boiler code. This also works when you have to change deeply nested components like the ChannelPreview
or Message
.
/* eslint-disable react/display-name */
/* eslint-disable react/display-name */
import React, {useContext, useEffect, useMemo, useState} from 'react';
import {
LogBox,
SafeAreaView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator, useHeaderHeight} from '@react-navigation/stack';
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import {StreamChat} from 'stream-chat';
import {
Channel,
ChannelAvatar,
ChannelList,
Chat,
MessageInput,
MessageList,
OverlayProvider,
useChannelsContext,
useAttachmentPickerContext,
} from 'stream-chat-react-native';
LogBox.ignoreAllLogs(true);
const styles = StyleSheet.create({
previewContainer: {
display: 'flex',
flexDirection: 'row',
borderBottomColor: '#EBEBEB',
borderBottomWidth: 1,
padding: 10,
},
previewTitle: {
textAlignVertical: 'center',
},
});
const chatClient = StreamChat.getInstance('q95x9hkbyd6p');
const userToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c';
const user = {
id: 'ron',
};
const filters = {
example: 'example-apps',
members: {$in: ['ron']},
type: 'messaging',
};
const sort = {last_message_at: -1};
const CustomChannelPreview = ({channel, setActiveChannel}) => {
const {onSelect} = useChannelsContext();
return (
<TouchableOpacity
style={styles.previewContainer}
onPress={() => onSelect(channel)}>
<ChannelAvatar channel={channel} />
<Text style={styles.previewTitle}>{channel.data.name}</Text>
</TouchableOpacity>
);
};
const ChannelListScreen = ({navigation}) => {
const {setChannel} = useContext(AppContext);
const memoizedFilters = useMemo(() => filters, []);
return (
<Chat client={chatClient}>
<View style={StyleSheet.absoluteFill}>
<ChannelList
filters={memoizedFilters}
onSelect={(channel) => {
setChannel(channel);
navigation.navigate('Channel');
}}
Preview={CustomChannelPreview}
sort={sort}
/>
</View>
</Chat>
);
};
const ChannelScreen = ({navigation}) => {
const {channel} = useContext(AppContext);
const headerHeight = useHeaderHeight();
const {setTopInset} = useAttachmentPickerContext();
useEffect(() => {
setTopInset(headerHeight);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [headerHeight]);
return (
<SafeAreaView>
<Chat client={chatClient}>
<Channel channel={channel} keyboardVerticalOffset={headerHeight}>
<View style={StyleSheet.absoluteFill}>
<MessageList />
<MessageInput />
</View>
</Channel>
</Chat>
</SafeAreaView>
);
};
const Stack = createStackNavigator();
const AppContext = React.createContext();
const App = () => {
const {bottom} = useSafeAreaInsets();
const [channel, setChannel] = useState();
const [clientReady, setClientReady] = useState(false);
const [thread, setThread] = useState();
useEffect(() => {
const setupClient = async () => {
await chatClient.connectUser(user, userToken);
setClientReady(true);
};
setupClient();
}, []);
return (
<NavigationContainer>
<AppContext.Provider value={{channel, setChannel, setThread, thread}}>
<OverlayProvider bottomInset={bottom}>
{clientReady && (
<Stack.Navigator
initialRouteName="ChannelList"
screenOptions={{
headerTitleStyle: {alignSelf: 'center', fontWeight: 'bold'},
}}>
<Stack.Screen
component={ChannelScreen}
name="Channel"
options={() => ({
headerBackTitle: 'Back',
headerRight: () => <></>,
headerTitle: channel?.data?.name,
})}
/>
<Stack.Screen
component={ChannelListScreen}
name="ChannelList"
options={{headerTitle: 'Channel List'}}
/>
</Stack.Navigator>
)}
</OverlayProvider>
</AppContext.Provider>
</NavigationContainer>
);
};
export default () => {
return (
<SafeAreaProvider>
<App />
</SafeAreaProvider>
);
};
Stream Chat supports message threads out of the box. Threads allows users to create sub-conversations inside the same channel.
Using threaded conversations is very simple and mostly a matter of plugging the Thread
component with React Navigation.
We created a new chat screen component called ThreadScreen
We registered the new chat screen to navigation
We pass the onThreadSelect
prop to MessageList
and use that to navigate to ThreadScreen
.
Now we can open threads and create new ones as well, if you long press a message you can tap on Reply and it will open the same ThreadScreen
.
/* eslint-disable react/display-name */
import React, {useContext, useEffect, useMemo, useState} from 'react';
import {LogBox, SafeAreaView, StyleSheet, View} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator, useHeaderHeight} from '@react-navigation/stack';
import {
SafeAreaProvider,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import {StreamChat} from 'stream-chat';
import {
Channel,
ChannelList,
Chat,
MessageInput,
MessageList,
OverlayProvider,
Thread,
useAttachmentPickerContext,
} from 'stream-chat-react-native';
LogBox.ignoreAllLogs(true);
const chatClient = StreamChat.getInstance('q95x9hkbyd6p');
const userToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c';
const user = {
id: 'ron',
};
const filters = {
example: 'example-apps',
members: {$in: ['ron']},
type: 'messaging',
};
const sort = {last_message_at: -1};
const ChannelListScreen = ({navigation}) => {
const {setChannel} = useContext(AppContext);
const memoizedFilters = useMemo(() => filters, []);
return (
<Chat client={chatClient}>
<View style={StyleSheet.absoluteFill}>
<ChannelList
filters={memoizedFilters}
onSelect={(channel) => {
setChannel(channel);
navigation.navigate('Channel');
}}
sort={sort}
/>
</View>
</Chat>
);
};
const ChannelScreen = ({navigation}) => {
const {channel, setThread, thread} = useContext(AppContext);
const headerHeight = useHeaderHeight();
const {setTopInset} = useAttachmentPickerContext();
useEffect(() => {
setTopInset(headerHeight);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [headerHeight]);
return (
<SafeAreaView>
<Chat client={chatClient}>
<Channel
channel={channel}
keyboardVerticalOffset={headerHeight}
thread={thread}>
<View style={StyleSheet.absoluteFill}>
<MessageList
onThreadSelect={(thread) => {
setThread(thread);
navigation.navigate('Thread', {
channelId: channel.id,
});
}}
/>
<MessageInput />
</View>
</Channel>
</Chat>
</SafeAreaView>
);
};
const ThreadScreen = ({route}) => {
const {thread} = useContext(AppContext);
const [channel] = useState(
chatClient.channel('messaging', route.params.channelId),
);
const headerHeight = useHeaderHeight();
return (
<SafeAreaView>
<Chat client={chatClient}>
<Channel
channel={channel}
keyboardVerticalOffset={headerHeight}
thread={thread}>
<View style={StyleSheet.absoluteFill}>
<Thread thread={thread} />
</View>
</Channel>
</Chat>
</SafeAreaView>
);
};
const Stack = createStackNavigator();
const AppContext = React.createContext();
const App = () => {
const {bottom} = useSafeAreaInsets();
const [channel, setChannel] = useState();
const [clientReady, setClientReady] = useState(false);
const [thread, setThread] = useState();
useEffect(() => {
const setupClient = async () => {
await chatClient.connectUser(user, userToken);
setClientReady(true);
};
setupClient();
}, []);
return (
<NavigationContainer>
<AppContext.Provider value={{channel, setChannel, setThread, thread}}>
<OverlayProvider bottomInset={bottom}>
{clientReady && (
<Stack.Navigator
initialRouteName="ChannelList"
screenOptions={{
headerTitleStyle: {alignSelf: 'center', fontWeight: 'bold'},
}}>
<Stack.Screen
component={ChannelScreen}
name="Channel"
options={() => ({
headerBackTitle: 'Back',
headerRight: () => <></>,
headerTitle: channel?.data?.name,
})}
/>
<Stack.Screen
component={ChannelListScreen}
name="ChannelList"
options={{headerTitle: 'Channel List'}}
/>
<Stack.Screen component={ThreadScreen} name="Thread" />
</Stack.Navigator>
)}
</OverlayProvider>
</AppContext.Provider>
</NavigationContainer>
);
};
export default () => {
return (
<SafeAreaProvider>
<App />
</SafeAreaProvider>
);
};
Customizing how messages are rendered is another very common use-case that the SDK supports easily.
Replace the built-in message component with your own is done by passing it as a prop to one of the parent components (eg. Channel
, ChannelList
, MessageList
).
Let’s make a very simple custom message component that uses a more compact layout for messages.
import React, {useEffect, useState} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {StreamChat} from 'stream-chat';
import {
Channel,
Chat,
MessageInput,
MessageList,
OverlayProvider as ChatOverlayProvider,
useMessageContext,
} from 'stream-chat-react-native';
import {
SafeAreaProvider,
SafeAreaView,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
const userToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c';
const user = {
id: 'ron',
};
const chatClient = StreamChat.getInstance('q95x9hkbyd6p');
const connectUserPromise = chatClient.connectUser(user, userToken);
const channel = chatClient.channel('messaging', 'channel_id');
const CustomMessage = (props) => {
const {message} = useMessageContext();
return (
<View style={{borderColor: 'black', borderWidth: 1}}>
<Text>{message.text}</Text>
</View>
);
};
const ChannelScreen = () => {
const {bottom} = useSafeAreaInsets();
return (
<ChatOverlayProvider bottomInset={bottom} topInset={0}>
<SafeAreaView>
<Chat client={chatClient}>
{/* Setting keyboardVerticalOffset as 0, since we don't have any header yet */}
<Channel
channel={channel}
keyboardVerticalOffset={0}
MessageSimple={CustomMessage}>
<View style={StyleSheet.absoluteFill}>
<MessageList />
<MessageInput />
</View>
</Channel>
</Chat>
</SafeAreaView>
</ChatOverlayProvider>
);
};
export default function App() {
const [ready, setReady] = useState();
useEffect(() => {
const initChat = async () => {
await connectUserPromise;
await channel.watch();
setReady(true);
};
initChat();
}, []);
if (!ready) {
return null;
}
return (
<SafeAreaProvider>
<ChannelScreen channel={channel} />
</SafeAreaProvider>
);
}
Please checkout react-native cookbook for many more examples of message customizations.
This SDK provides quite rich UI components in terms of design and functionality. But sometimes you may want to replace the default components with something that better fits your application requirements. For this purpose, we have made it quite easy to either replace the existing components with custom components or add your own styling on existing components
In this Chat App tutorial we built a fully functioning React Native Chat app with our Chat SDK component library. We also showed how easy it is to customize the behavior and the style of the {platform} chat app components with minimal code changes. Both the Chat SDK for react native and the Chat API have plenty more features available to support more advanced use-cases such as push notifications, content moderation, rich messages and more. For web, check out our React Chat tutorial and for iOS check out iOS Swift Chat. If you want some inspiration for your app, download our free chat interface UI kit.